@typescript-eslint/eslint-plugin 8.46.5-alpha.1 → 8.46.5-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -103,6 +103,8 @@ declare const _default: {
103
103
  '@typescript-eslint/no-unsafe-unary-minus': "error";
104
104
  'no-unused-expressions': "off";
105
105
  '@typescript-eslint/no-unused-expressions': "error";
106
+ 'no-unused-private-class-members': "off";
107
+ '@typescript-eslint/no-unused-private-class-members': "error";
106
108
  'no-unused-vars': "off";
107
109
  '@typescript-eslint/no-unused-vars': "error";
108
110
  'no-use-before-define': "off";
@@ -110,6 +110,8 @@ module.exports = {
110
110
  '@typescript-eslint/no-unsafe-unary-minus': 'error',
111
111
  'no-unused-expressions': 'off',
112
112
  '@typescript-eslint/no-unused-expressions': 'error',
113
+ 'no-unused-private-class-members': 'off',
114
+ '@typescript-eslint/no-unused-private-class-members': 'error',
113
115
  'no-unused-vars': 'off',
114
116
  '@typescript-eslint/no-unused-vars': 'error',
115
117
  'no-use-before-define': 'off',
@@ -123,6 +123,8 @@ exports.default = (plugin, parser) => [
123
123
  '@typescript-eslint/no-unsafe-unary-minus': 'error',
124
124
  'no-unused-expressions': 'off',
125
125
  '@typescript-eslint/no-unused-expressions': 'error',
126
+ 'no-unused-private-class-members': 'off',
127
+ '@typescript-eslint/no-unused-private-class-members': 'error',
126
128
  'no-unused-vars': 'off',
127
129
  '@typescript-eslint/no-unused-vars': 'error',
128
130
  'no-use-before-define': 'off',
package/dist/index.d.ts CHANGED
@@ -105,6 +105,8 @@ declare const _default: {
105
105
  '@typescript-eslint/no-unsafe-unary-minus': "error";
106
106
  'no-unused-expressions': "off";
107
107
  '@typescript-eslint/no-unused-expressions': "error";
108
+ 'no-unused-private-class-members': "off";
109
+ '@typescript-eslint/no-unused-private-class-members': "error";
108
110
  'no-unused-vars': "off";
109
111
  '@typescript-eslint/no-unused-vars': "error";
110
112
  'no-use-before-define': "off";
@@ -794,6 +796,7 @@ declare const _default: {
794
796
  allowTaggedTemplates?: boolean;
795
797
  allowTernary?: boolean;
796
798
  }], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
799
+ 'no-unused-private-class-members': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedPrivateClassMember", [], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
797
800
  'no-unused-vars': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-unused-vars").MessageIds, import("./rules/no-unused-vars").Options, import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
798
801
  'no-use-before-define': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUseBeforeDefine", import("./rules/no-use-before-define").Options, import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
799
802
  'no-useless-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -127,6 +127,8 @@ declare const _default: {
127
127
  '@typescript-eslint/no-unsafe-unary-minus': "error";
128
128
  'no-unused-expressions': "off";
129
129
  '@typescript-eslint/no-unused-expressions': "error";
130
+ 'no-unused-private-class-members': "off";
131
+ '@typescript-eslint/no-unused-private-class-members': "error";
130
132
  'no-unused-vars': "off";
131
133
  '@typescript-eslint/no-unused-vars': "error";
132
134
  'no-use-before-define': "off";
@@ -817,6 +819,7 @@ declare const _default: {
817
819
  allowTaggedTemplates?: boolean;
818
820
  allowTernary?: boolean;
819
821
  }], import("../rules").ESLintPluginDocs, TSESLint.RuleListener>;
822
+ 'no-unused-private-class-members': TSESLint.RuleModule<"unusedPrivateClassMember", [], import("../rules").ESLintPluginDocs, TSESLint.RuleListener>;
820
823
  'no-unused-vars': TSESLint.RuleModule<import("./rules/no-unused-vars").MessageIds, import("./rules/no-unused-vars").Options, import("../rules").ESLintPluginDocs, TSESLint.RuleListener>;
821
824
  'no-use-before-define': TSESLint.RuleModule<"noUseBeforeDefine", import("./rules/no-use-before-define").Options, import("../rules").ESLintPluginDocs, TSESLint.RuleListener>;
822
825
  'no-useless-constructor': TSESLint.RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../rules").ESLintPluginDocs, TSESLint.RuleListener>;
@@ -124,6 +124,7 @@ declare const rules: {
124
124
  allowTaggedTemplates?: boolean;
125
125
  allowTernary?: boolean;
126
126
  }], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
127
+ 'no-unused-private-class-members': import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedPrivateClassMember", [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
127
128
  'no-unused-vars': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./no-unused-vars").MessageIds, import("./no-unused-vars").Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
128
129
  'no-use-before-define': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUseBeforeDefine", import("./no-use-before-define").Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
129
130
  'no-useless-constructor': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noUselessConstructor" | "removeConstructor", [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -90,6 +90,7 @@ const no_unsafe_return_1 = __importDefault(require("./no-unsafe-return"));
90
90
  const no_unsafe_type_assertion_1 = __importDefault(require("./no-unsafe-type-assertion"));
91
91
  const no_unsafe_unary_minus_1 = __importDefault(require("./no-unsafe-unary-minus"));
92
92
  const no_unused_expressions_1 = __importDefault(require("./no-unused-expressions"));
93
+ const no_unused_private_class_members_1 = __importDefault(require("./no-unused-private-class-members"));
93
94
  const no_unused_vars_1 = __importDefault(require("./no-unused-vars"));
94
95
  const no_use_before_define_1 = __importDefault(require("./no-use-before-define"));
95
96
  const no_useless_constructor_1 = __importDefault(require("./no-useless-constructor"));
@@ -222,6 +223,7 @@ const rules = {
222
223
  'no-unsafe-type-assertion': no_unsafe_type_assertion_1.default,
223
224
  'no-unsafe-unary-minus': no_unsafe_unary_minus_1.default,
224
225
  'no-unused-expressions': no_unused_expressions_1.default,
226
+ 'no-unused-private-class-members': no_unused_private_class_members_1.default,
225
227
  'no-unused-vars': no_unused_vars_1.default,
226
228
  'no-use-before-define': no_use_before_define_1.default,
227
229
  'no-useless-constructor': no_useless_constructor_1.default,
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ export type MessageIds = 'unusedPrivateClassMember';
3
+ declare const _default: ESLintUtils.RuleModule<"unusedPrivateClassMember", [], import("../../rules").ESLintPluginDocs, ESLintUtils.RuleListener>;
4
+ export default _default;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@typescript-eslint/utils");
4
+ const util_1 = require("../util");
5
+ const classScopeAnalyzer_1 = require("../util/class-scope-analyzer/classScopeAnalyzer");
6
+ exports.default = (0, util_1.createRule)({
7
+ name: 'no-unused-private-class-members',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Disallow unused private class members',
12
+ extendsBaseRule: true,
13
+ requiresTypeChecking: false,
14
+ },
15
+ messages: {
16
+ unusedPrivateClassMember: "Private class member '{{classMemberName}}' is defined but never used.",
17
+ },
18
+ schema: [],
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ 'Program:exit'(node) {
24
+ const result = (0, classScopeAnalyzer_1.analyzeClassMemberUsage)(node, utils_1.ESLintUtils.nullThrows(context.sourceCode.scopeManager, 'Missing required scope manager'));
25
+ for (const classScope of result.values()) {
26
+ for (const member of [
27
+ ...classScope.members.instance.values(),
28
+ ...classScope.members.static.values(),
29
+ ]) {
30
+ if ((!member.isPrivate() && !member.isHashPrivate()) ||
31
+ member.isUsed()) {
32
+ continue;
33
+ }
34
+ context.report({
35
+ node: member.nameNode,
36
+ messageId: 'unusedPrivateClassMember',
37
+ data: { classMemberName: member.name },
38
+ });
39
+ }
40
+ }
41
+ },
42
+ };
43
+ },
44
+ });
@@ -0,0 +1,52 @@
1
+ import type { ScopeManager } from '@typescript-eslint/scope-manager';
2
+ import type { TSESTree } from '@typescript-eslint/utils';
3
+ import type { ClassNode, Key, MemberNode } from './types';
4
+ export declare class Member {
5
+ /**
6
+ * The node that declares this member
7
+ */
8
+ readonly node: MemberNode;
9
+ /**
10
+ * The resolved, unique key for this member.
11
+ */
12
+ readonly key: Key;
13
+ /**
14
+ * The member name, as given in the source code.
15
+ */
16
+ readonly name: string;
17
+ /**
18
+ * The node that represents the member name in the source code.
19
+ * Used for reporting errors.
20
+ */
21
+ readonly nameNode: TSESTree.Node;
22
+ /**
23
+ * The number of writes to this member.
24
+ */
25
+ writeCount: number;
26
+ /**
27
+ * The number of reads from this member.
28
+ */
29
+ readCount: number;
30
+ private constructor();
31
+ static create(node: MemberNode): Member | null;
32
+ isAccessor(): boolean;
33
+ isHashPrivate(): boolean;
34
+ isPrivate(): boolean;
35
+ isStatic(): boolean;
36
+ isUsed(): boolean;
37
+ }
38
+ export interface ClassScopeResult {
39
+ /**
40
+ * The classes name as given in the source code.
41
+ * If this is `null` then the class is an anonymous class.
42
+ */
43
+ readonly className: string | null;
44
+ /**
45
+ * The class's members, keyed by their name
46
+ */
47
+ readonly members: {
48
+ readonly instance: Map<Key, Member>;
49
+ readonly static: Map<Key, Member>;
50
+ };
51
+ }
52
+ export declare function analyzeClassMemberUsage(program: TSESTree.Program, scopeManager: ScopeManager): ReadonlyMap<ClassNode, ClassScopeResult>;
@@ -0,0 +1,519 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Member = void 0;
4
+ exports.analyzeClassMemberUsage = analyzeClassMemberUsage;
5
+ const scope_manager_1 = require("@typescript-eslint/scope-manager");
6
+ const utils_1 = require("@typescript-eslint/utils");
7
+ const __1 = require("..");
8
+ const extractComputedName_1 = require("./extractComputedName");
9
+ const types_1 = require("./types");
10
+ class Member {
11
+ /**
12
+ * The node that declares this member
13
+ */
14
+ node;
15
+ /**
16
+ * The resolved, unique key for this member.
17
+ */
18
+ key;
19
+ /**
20
+ * The member name, as given in the source code.
21
+ */
22
+ name;
23
+ /**
24
+ * The node that represents the member name in the source code.
25
+ * Used for reporting errors.
26
+ */
27
+ nameNode;
28
+ /**
29
+ * The number of writes to this member.
30
+ */
31
+ writeCount = 0;
32
+ /**
33
+ * The number of reads from this member.
34
+ */
35
+ readCount = 0;
36
+ constructor(node, key, name, nameNode) {
37
+ this.node = node;
38
+ this.key = key;
39
+ this.name = name;
40
+ this.nameNode = nameNode;
41
+ }
42
+ static create(node) {
43
+ const name = (0, extractComputedName_1.extractNameForMember)(node);
44
+ if (name == null) {
45
+ return null;
46
+ }
47
+ return new Member(node, name.key, name.codeName, name.nameNode);
48
+ }
49
+ isAccessor() {
50
+ if (this.node.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
51
+ this.node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
52
+ return this.node.kind === 'set' || this.node.kind === 'get';
53
+ }
54
+ return (this.node.type === utils_1.AST_NODE_TYPES.AccessorProperty ||
55
+ this.node.type === utils_1.AST_NODE_TYPES.TSAbstractAccessorProperty);
56
+ }
57
+ isHashPrivate() {
58
+ return ('key' in this.node &&
59
+ this.node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier);
60
+ }
61
+ isPrivate() {
62
+ return this.node.accessibility === 'private';
63
+ }
64
+ isStatic() {
65
+ return this.node.static;
66
+ }
67
+ isUsed() {
68
+ return (this.readCount > 0 ||
69
+ // any usage of an accessor is considered a usage as accessor can have side effects
70
+ (this.writeCount > 0 && this.isAccessor()));
71
+ }
72
+ }
73
+ exports.Member = Member;
74
+ function isWriteOnlyUsage(node, parent) {
75
+ if (parent.type !== utils_1.AST_NODE_TYPES.AssignmentExpression &&
76
+ parent.type !== utils_1.AST_NODE_TYPES.ForInStatement &&
77
+ parent.type !== utils_1.AST_NODE_TYPES.ForOfStatement &&
78
+ parent.type !== utils_1.AST_NODE_TYPES.AssignmentPattern) {
79
+ return false;
80
+ }
81
+ // If it's on the right then it's a read not a write
82
+ if (parent.left !== node) {
83
+ return false;
84
+ }
85
+ if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
86
+ // For any other operator (such as '+=') we still consider it a read operation
87
+ parent.operator !== '=') {
88
+ // if the read operation is "discarded" in an empty statement, then it is write only.
89
+ return parent.parent.type === utils_1.AST_NODE_TYPES.ExpressionStatement;
90
+ }
91
+ return true;
92
+ }
93
+ function countReference(identifierParent, member) {
94
+ const identifierGrandparent = (0, __1.nullThrows)(identifierParent.parent, __1.NullThrowsReasons.MissingParent);
95
+ if (isWriteOnlyUsage(identifierParent, identifierGrandparent)) {
96
+ member.writeCount += 1;
97
+ return;
98
+ }
99
+ const identifierGreatGrandparent = identifierGrandparent.parent;
100
+ // A statement which only increments (`this.#x++;`)
101
+ if (identifierGrandparent.type === utils_1.AST_NODE_TYPES.UpdateExpression &&
102
+ identifierGreatGrandparent?.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
103
+ member.writeCount += 1;
104
+ return;
105
+ }
106
+ /*
107
+ * ({ x: this.#usedInDestructuring } = bar);
108
+ *
109
+ * But should treat the following as a read:
110
+ * ({ [this.#x]: a } = foo);
111
+ */
112
+ if (identifierGrandparent.type === utils_1.AST_NODE_TYPES.Property &&
113
+ identifierGreatGrandparent?.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
114
+ identifierGrandparent.value === identifierParent) {
115
+ member.writeCount += 1;
116
+ return;
117
+ }
118
+ // [...this.#unusedInRestPattern] = bar;
119
+ if (identifierGrandparent.type === utils_1.AST_NODE_TYPES.RestElement) {
120
+ member.writeCount += 1;
121
+ return;
122
+ }
123
+ // [this.#unusedInAssignmentPattern] = bar;
124
+ if (identifierGrandparent.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
125
+ member.writeCount += 1;
126
+ return;
127
+ }
128
+ member.readCount += 1;
129
+ }
130
+ class ThisScope extends scope_manager_1.Visitor {
131
+ /**
132
+ * True if the context is considered a static context and so `this` refers to
133
+ * the class and not an instance (eg a static method or a static block).
134
+ */
135
+ isStaticThisContext;
136
+ /**
137
+ * The classes directly declared within this class -- for example a class declared within a method.
138
+ * This does not include grandchild classes.
139
+ */
140
+ childScopes = [];
141
+ /**
142
+ * The scope manager instance used to resolve variables to improve discovery of usages.
143
+ */
144
+ scopeManager;
145
+ /**
146
+ * The parent class scope if one exists.
147
+ */
148
+ upper;
149
+ /**
150
+ * The context of the `this` reference in the current scope.
151
+ */
152
+ thisContext;
153
+ constructor(scopeManager, upper, thisContext, isStaticThisContext) {
154
+ super({});
155
+ this.scopeManager = scopeManager;
156
+ this.upper = upper;
157
+ this.isStaticThisContext = isStaticThisContext;
158
+ if (thisContext === 'self') {
159
+ if (!(this instanceof ClassScope)) {
160
+ throw new Error('Cannot use `self` unless it is in a ClassScope');
161
+ }
162
+ this.thisContext = this;
163
+ }
164
+ else if (thisContext === 'none') {
165
+ this.thisContext = null;
166
+ }
167
+ else {
168
+ this.thisContext = thisContext;
169
+ }
170
+ }
171
+ findNearestScope(node) {
172
+ let currentScope;
173
+ let currentNode = node;
174
+ do {
175
+ currentScope = this.scopeManager.acquire(currentNode);
176
+ if (currentNode.parent == null) {
177
+ break;
178
+ }
179
+ currentNode = currentNode.parent;
180
+ } while (currentScope == null);
181
+ return currentScope;
182
+ }
183
+ findVariableInScope(node, name) {
184
+ let currentScope = this.findNearestScope(node);
185
+ let variable = null;
186
+ while (currentScope != null) {
187
+ variable = currentScope.set.get(name) ?? null;
188
+ if (variable != null) {
189
+ break;
190
+ }
191
+ currentScope = currentScope.upper;
192
+ }
193
+ return variable;
194
+ }
195
+ getObjectClass(node) {
196
+ switch (node.object.type) {
197
+ case utils_1.AST_NODE_TYPES.ThisExpression: {
198
+ if (this.thisContext == null) {
199
+ return null;
200
+ }
201
+ return {
202
+ thisContext: this.thisContext,
203
+ type: this.isStaticThisContext ? 'static' : 'instance',
204
+ };
205
+ }
206
+ case utils_1.AST_NODE_TYPES.Identifier: {
207
+ const thisContext = this.findClassScopeWithName(node.object.name);
208
+ if (thisContext != null) {
209
+ return { thisContext, type: 'static' };
210
+ }
211
+ // the following code does some very rudimentary scope analysis to handle some trivial cases
212
+ const variable = this.findVariableInScope(node, node.object.name);
213
+ if (variable == null || variable.defs.length === 0) {
214
+ return null;
215
+ }
216
+ const firstDef = variable.defs[0];
217
+ switch (firstDef.node.type) {
218
+ // detect simple reassignment of `this`
219
+ // ```
220
+ // class Foo {
221
+ // private prop: number;
222
+ // method(thing: Foo) {
223
+ // const self = this;
224
+ // return self.prop;
225
+ // }
226
+ // }
227
+ // ```
228
+ case utils_1.AST_NODE_TYPES.VariableDeclarator: {
229
+ const value = firstDef.node.init;
230
+ if (value == null || value.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
231
+ return null;
232
+ }
233
+ if (variable.references.some(ref => ref.isWrite() && ref.init !== true)) {
234
+ // variable is assigned to multiple times so we can't be sure that it's still the same class
235
+ return null;
236
+ }
237
+ // we have a case like `const self = this` or `let self = this` that is not reassigned
238
+ // so we can safely assume that it's still the same class!
239
+ return {
240
+ thisContext: this.thisContext,
241
+ type: this.isStaticThisContext ? 'static' : 'instance',
242
+ };
243
+ }
244
+ // Look for variables typed as the current class:
245
+ // ```
246
+ // class Foo {
247
+ // private prop: number;
248
+ // method(thing: Foo) {
249
+ // // this references the private instance member but not via `this` so we can't see it
250
+ // thing.prop = 1;
251
+ // }
252
+ // }
253
+ // ```
254
+ default: {
255
+ const typeAnnotation = (() => {
256
+ if ('typeAnnotation' in firstDef.name &&
257
+ firstDef.name.typeAnnotation != null) {
258
+ return firstDef.name.typeAnnotation.typeAnnotation;
259
+ }
260
+ return null;
261
+ })();
262
+ if (typeAnnotation == null) {
263
+ return null;
264
+ }
265
+ // Cases like `method(thing: Foo) { ... }`
266
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
267
+ typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
268
+ const typeName = typeAnnotation.typeName.name;
269
+ const typeScope = this.findClassScopeWithName(typeName);
270
+ if (typeScope != null) {
271
+ return { thisContext: typeScope, type: 'instance' };
272
+ }
273
+ }
274
+ // Cases like `method(thing: typeof Foo) { ... }`
275
+ if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeQuery &&
276
+ typeAnnotation.exprName.type === utils_1.AST_NODE_TYPES.Identifier) {
277
+ const exprName = typeAnnotation.exprName.name;
278
+ const exprScope = this.findClassScopeWithName(exprName);
279
+ if (exprScope != null) {
280
+ return { thisContext: exprScope, type: 'static' };
281
+ }
282
+ }
283
+ }
284
+ }
285
+ return null;
286
+ }
287
+ case utils_1.AST_NODE_TYPES.MemberExpression:
288
+ // TODO - we could probably recurse here to do some more complex analysis and support like `foo.bar.baz` nested references
289
+ return null;
290
+ default:
291
+ return null;
292
+ }
293
+ }
294
+ visitClass(node) {
295
+ const classScope = new ClassScope(node, this, this.scopeManager);
296
+ this.childScopes.push(classScope);
297
+ classScope.visitChildren(node);
298
+ }
299
+ visitIntermediate(node) {
300
+ const intermediateScope = new IntermediateScope(this.scopeManager, this, node);
301
+ this.childScopes.push(intermediateScope);
302
+ intermediateScope.visitChildren(node);
303
+ }
304
+ /**
305
+ * Gets the nearest class scope with the given name.
306
+ */
307
+ findClassScopeWithName(name) {
308
+ let currentScope = this;
309
+ while (currentScope != null) {
310
+ if (currentScope instanceof ClassScope &&
311
+ currentScope.className === name) {
312
+ return currentScope;
313
+ }
314
+ currentScope = currentScope.upper;
315
+ }
316
+ return null;
317
+ }
318
+ /////////////////////
319
+ // Visit selectors //
320
+ /////////////////////
321
+ ClassDeclaration(node) {
322
+ this.visitClass(node);
323
+ }
324
+ ClassExpression(node) {
325
+ this.visitClass(node);
326
+ }
327
+ FunctionDeclaration(node) {
328
+ this.visitIntermediate(node);
329
+ }
330
+ FunctionExpression(node) {
331
+ this.visitIntermediate(node);
332
+ }
333
+ MemberExpression(node) {
334
+ this.visitChildren(node);
335
+ if (node.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
336
+ // will be handled by the PrivateIdentifier visitor
337
+ return;
338
+ }
339
+ const propertyName = (0, extractComputedName_1.extractNameForMemberExpression)(node);
340
+ if (propertyName == null) {
341
+ return;
342
+ }
343
+ const objectClassName = this.getObjectClass(node);
344
+ if (objectClassName == null) {
345
+ return;
346
+ }
347
+ if (objectClassName.thisContext == null) {
348
+ return;
349
+ }
350
+ const members = objectClassName.type === 'instance'
351
+ ? objectClassName.thisContext.members.instance
352
+ : objectClassName.thisContext.members.static;
353
+ const member = members.get(propertyName.key);
354
+ if (member == null) {
355
+ return;
356
+ }
357
+ countReference(node, member);
358
+ }
359
+ PrivateIdentifier(node) {
360
+ this.visitChildren(node);
361
+ if ((node.parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
362
+ node.parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
363
+ node.parent.key === node) {
364
+ // ignore the member definition
365
+ return;
366
+ }
367
+ // We can actually be pretty loose with our code here thanks to how private
368
+ // members are designed.
369
+ //
370
+ // 1) classes CANNOT have a static and instance private member with the
371
+ // same name, so we don't need to match up static access.
372
+ // 2) nested classes CANNOT access a private member of their parent class if
373
+ // the member has the same name as a private member of the nested class.
374
+ //
375
+ // together this means that we can just look for the member upwards until we
376
+ // find a match and we know that will be the correct match!
377
+ let currentScope = this;
378
+ const key = (0, types_1.privateKey)(node);
379
+ while (currentScope != null) {
380
+ if (currentScope.thisContext != null) {
381
+ const member = currentScope.thisContext.members.instance.get(key) ??
382
+ currentScope.thisContext.members.static.get(key);
383
+ if (member != null) {
384
+ countReference(node.parent, member);
385
+ return;
386
+ }
387
+ }
388
+ currentScope = currentScope.upper;
389
+ }
390
+ }
391
+ StaticBlock(node) {
392
+ this.visitIntermediate(node);
393
+ }
394
+ }
395
+ /**
396
+ * Any other scope that is not a class scope
397
+ *
398
+ * When we visit a function declaration/expression the `this` reference is
399
+ * rebound so it no longer refers to the class.
400
+ *
401
+ * This also supports a function's `this` parameter.
402
+ */
403
+ class IntermediateScope extends ThisScope {
404
+ constructor(scopeManager, upper, node) {
405
+ if (node.type === utils_1.AST_NODE_TYPES.Program) {
406
+ super(scopeManager, upper, 'none', false);
407
+ return;
408
+ }
409
+ if (node.type === utils_1.AST_NODE_TYPES.StaticBlock) {
410
+ if (upper == null || !(upper instanceof ClassScope)) {
411
+ throw new Error('Cannot have a static block without an upper ClassScope');
412
+ }
413
+ super(scopeManager, upper, upper, true);
414
+ return;
415
+ }
416
+ // method definition
417
+ if ((node.parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
418
+ node.parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
419
+ node.parent.value === node) {
420
+ if (upper == null || !(upper instanceof ClassScope)) {
421
+ throw new Error('Cannot have a class method/property without an upper ClassScope');
422
+ }
423
+ super(scopeManager, upper, upper, node.parent.static);
424
+ return;
425
+ }
426
+ // function with a `this` parameter
427
+ if (upper != null &&
428
+ node.params.length > 0 &&
429
+ node.params[0].type === utils_1.AST_NODE_TYPES.Identifier &&
430
+ node.params[0].name === 'this') {
431
+ const thisType = node.params[0].typeAnnotation?.typeAnnotation;
432
+ if (thisType?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
433
+ thisType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
434
+ const thisContext = upper.findClassScopeWithName(thisType.typeName.name);
435
+ if (thisContext != null) {
436
+ super(scopeManager, upper, thisContext, false);
437
+ return;
438
+ }
439
+ }
440
+ }
441
+ super(scopeManager, upper, 'none', false);
442
+ }
443
+ }
444
+ class ClassScope extends ThisScope {
445
+ className;
446
+ /**
447
+ * The class's members, keyed by their name
448
+ */
449
+ members = {
450
+ instance: new Map(),
451
+ static: new Map(),
452
+ };
453
+ /**
454
+ * The node that declares this class.
455
+ */
456
+ theClass;
457
+ constructor(theClass, upper, scopeManager) {
458
+ super(scopeManager, upper, 'self', false);
459
+ this.theClass = theClass;
460
+ this.className = theClass.id?.name ?? null;
461
+ for (const memberNode of theClass.body.body) {
462
+ switch (memberNode.type) {
463
+ case utils_1.AST_NODE_TYPES.MethodDefinition:
464
+ if (memberNode.kind === 'constructor') {
465
+ for (const parameter of memberNode.value.params) {
466
+ if (parameter.type !== utils_1.AST_NODE_TYPES.TSParameterProperty) {
467
+ continue;
468
+ }
469
+ const member = Member.create(parameter);
470
+ if (member == null) {
471
+ continue;
472
+ }
473
+ this.members.instance.set(member.key, member);
474
+ }
475
+ // break instead of falling through because the constructor is not a "member" we track
476
+ break;
477
+ }
478
+ // intentional fallthrough
479
+ case utils_1.AST_NODE_TYPES.AccessorProperty:
480
+ case utils_1.AST_NODE_TYPES.PropertyDefinition:
481
+ case utils_1.AST_NODE_TYPES.TSAbstractAccessorProperty:
482
+ case utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition:
483
+ case utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition: {
484
+ const member = Member.create(memberNode);
485
+ if (member == null) {
486
+ continue;
487
+ }
488
+ if (member.isStatic()) {
489
+ this.members.static.set(member.key, member);
490
+ }
491
+ else {
492
+ this.members.instance.set(member.key, member);
493
+ }
494
+ break;
495
+ }
496
+ case utils_1.AST_NODE_TYPES.StaticBlock:
497
+ // static blocks declare no members
498
+ continue;
499
+ case utils_1.AST_NODE_TYPES.TSIndexSignature:
500
+ // index signatures are type signatures only and are fully computed
501
+ continue;
502
+ }
503
+ }
504
+ }
505
+ }
506
+ function analyzeClassMemberUsage(program, scopeManager) {
507
+ const rootScope = new IntermediateScope(scopeManager, null, program);
508
+ rootScope.visit(program);
509
+ return traverseScopes(rootScope);
510
+ }
511
+ function traverseScopes(currentScope, analysisResults = new Map()) {
512
+ if (currentScope instanceof ClassScope) {
513
+ analysisResults.set(currentScope.theClass, currentScope);
514
+ }
515
+ for (const childScope of currentScope.childScopes) {
516
+ traverseScopes(childScope, analysisResults);
517
+ }
518
+ return analysisResults;
519
+ }
@@ -0,0 +1,17 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ import type { Key, MemberNode } from './types';
3
+ export interface ExtractedName {
4
+ key: Key;
5
+ codeName: string;
6
+ nameNode: TSESTree.Node;
7
+ }
8
+ /**
9
+ * Extracts the string name for a member.
10
+ * @returns `null` if the name cannot be extracted due to it being computed.
11
+ */
12
+ export declare function extractNameForMember(node: MemberNode): ExtractedName | null;
13
+ /**
14
+ * Extracts the string property name for a member.
15
+ * @returns `null` if the name cannot be extracted due to it being a computed.
16
+ */
17
+ export declare function extractNameForMemberExpression(node: TSESTree.MemberExpression): ExtractedName | null;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractNameForMember = extractNameForMember;
4
+ exports.extractNameForMemberExpression = extractNameForMemberExpression;
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ const types_1 = require("./types");
7
+ function extractComputedName(computedName) {
8
+ if (computedName.type === utils_1.AST_NODE_TYPES.Literal) {
9
+ const name = computedName.value?.toString() ?? 'null';
10
+ return {
11
+ codeName: name,
12
+ key: (0, types_1.publicKey)(name),
13
+ nameNode: computedName,
14
+ };
15
+ }
16
+ if (computedName.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
17
+ computedName.expressions.length === 0) {
18
+ const name = computedName.quasis[0].value.raw;
19
+ return {
20
+ codeName: name,
21
+ key: (0, types_1.publicKey)(name),
22
+ nameNode: computedName,
23
+ };
24
+ }
25
+ return null;
26
+ }
27
+ function extractNonComputedName(nonComputedName) {
28
+ const name = nonComputedName.name;
29
+ if (nonComputedName.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
30
+ return {
31
+ codeName: `#${name}`,
32
+ key: (0, types_1.privateKey)(nonComputedName),
33
+ nameNode: nonComputedName,
34
+ };
35
+ }
36
+ return {
37
+ codeName: name,
38
+ key: (0, types_1.publicKey)(name),
39
+ nameNode: nonComputedName,
40
+ };
41
+ }
42
+ /**
43
+ * Extracts the string name for a member.
44
+ * @returns `null` if the name cannot be extracted due to it being computed.
45
+ */
46
+ function extractNameForMember(node) {
47
+ if (node.type === utils_1.AST_NODE_TYPES.TSParameterProperty) {
48
+ switch (node.parameter.type) {
49
+ case utils_1.AST_NODE_TYPES.ArrayPattern:
50
+ case utils_1.AST_NODE_TYPES.ObjectPattern:
51
+ case utils_1.AST_NODE_TYPES.RestElement:
52
+ // Nonsensical properties -- see https://github.com/typescript-eslint/typescript-eslint/issues/11708
53
+ return null;
54
+ case utils_1.AST_NODE_TYPES.AssignmentPattern:
55
+ if (node.parameter.left.type !== utils_1.AST_NODE_TYPES.Identifier) {
56
+ return null;
57
+ }
58
+ return extractNonComputedName(node.parameter.left);
59
+ case utils_1.AST_NODE_TYPES.Identifier:
60
+ return extractNonComputedName(node.parameter);
61
+ }
62
+ }
63
+ if (node.computed) {
64
+ return extractComputedName(node.key);
65
+ }
66
+ if (node.key.type === utils_1.AST_NODE_TYPES.Literal) {
67
+ return extractComputedName(node.key);
68
+ }
69
+ return extractNonComputedName(node.key);
70
+ }
71
+ /**
72
+ * Extracts the string property name for a member.
73
+ * @returns `null` if the name cannot be extracted due to it being a computed.
74
+ */
75
+ function extractNameForMemberExpression(node) {
76
+ if (node.computed) {
77
+ return extractComputedName(node.property);
78
+ }
79
+ return extractNonComputedName(node.property);
80
+ }
@@ -0,0 +1,12 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ export type ClassNode = TSESTree.ClassDeclaration | TSESTree.ClassExpression;
3
+ export type MemberNode = TSESTree.AccessorProperty | TSESTree.MethodDefinition | TSESTree.PropertyDefinition | TSESTree.TSAbstractAccessorProperty | TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractPropertyDefinition | TSESTree.TSParameterProperty;
4
+ export type PrivateKey = string & {
5
+ __brand: 'private-key';
6
+ };
7
+ export type PublicKey = string & {
8
+ __brand: 'public-key';
9
+ };
10
+ export type Key = PrivateKey | PublicKey;
11
+ export declare function publicKey(name: string): PublicKey;
12
+ export declare function privateKey(node: TSESTree.PrivateIdentifier): PrivateKey;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.publicKey = publicKey;
4
+ exports.privateKey = privateKey;
5
+ function publicKey(name) {
6
+ return name;
7
+ }
8
+ function privateKey(node) {
9
+ return `#private@@${node.name}`;
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.46.5-alpha.1",
3
+ "version": "8.46.5-alpha.3",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -59,10 +59,10 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@eslint-community/regexpp": "^4.10.0",
62
- "@typescript-eslint/scope-manager": "8.46.5-alpha.1",
63
- "@typescript-eslint/type-utils": "8.46.5-alpha.1",
64
- "@typescript-eslint/utils": "8.46.5-alpha.1",
65
- "@typescript-eslint/visitor-keys": "8.46.5-alpha.1",
62
+ "@typescript-eslint/scope-manager": "8.46.5-alpha.3",
63
+ "@typescript-eslint/type-utils": "8.46.5-alpha.3",
64
+ "@typescript-eslint/utils": "8.46.5-alpha.3",
65
+ "@typescript-eslint/visitor-keys": "8.46.5-alpha.3",
66
66
  "graphemer": "^1.4.0",
67
67
  "ignore": "^7.0.0",
68
68
  "natural-compare": "^1.4.0",
@@ -71,8 +71,8 @@
71
71
  "devDependencies": {
72
72
  "@types/mdast": "^4.0.3",
73
73
  "@types/natural-compare": "*",
74
- "@typescript-eslint/rule-schema-to-typescript-types": "8.46.5-alpha.1",
75
- "@typescript-eslint/rule-tester": "8.46.5-alpha.1",
74
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.46.5-alpha.3",
75
+ "@typescript-eslint/rule-tester": "8.46.5-alpha.3",
76
76
  "@vitest/coverage-v8": "^3.1.3",
77
77
  "ajv": "^6.12.6",
78
78
  "cross-fetch": "*",
@@ -92,7 +92,7 @@
92
92
  "vitest": "^3.1.3"
93
93
  },
94
94
  "peerDependencies": {
95
- "@typescript-eslint/parser": "^8.46.5-alpha.1",
95
+ "@typescript-eslint/parser": "^8.46.5-alpha.3",
96
96
  "eslint": "^8.57.0 || ^9.0.0",
97
97
  "typescript": ">=4.8.4 <6.0.0"
98
98
  },