source: imaps-frontend/node_modules/eslint/lib/rules/no-unused-private-class-members.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 7.7 KB
Line 
1/**
2 * @fileoverview Rule to flag declared but unused private class members
3 * @author Tim van der Lippe
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12/** @type {import('../shared/types').Rule} */
13module.exports = {
14 meta: {
15 type: "problem",
16
17 docs: {
18 description: "Disallow unused private class members",
19 recommended: false,
20 url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members"
21 },
22
23 schema: [],
24
25 messages: {
26 unusedPrivateClassMember: "'{{classMemberName}}' is defined but never used."
27 }
28 },
29
30 create(context) {
31 const trackedClasses = [];
32
33 /**
34 * Check whether the current node is in a write only assignment.
35 * @param {ASTNode} privateIdentifierNode Node referring to a private identifier
36 * @returns {boolean} Whether the node is in a write only assignment
37 * @private
38 */
39 function isWriteOnlyAssignment(privateIdentifierNode) {
40 const parentStatement = privateIdentifierNode.parent.parent;
41 const isAssignmentExpression = parentStatement.type === "AssignmentExpression";
42
43 if (!isAssignmentExpression &&
44 parentStatement.type !== "ForInStatement" &&
45 parentStatement.type !== "ForOfStatement" &&
46 parentStatement.type !== "AssignmentPattern") {
47 return false;
48 }
49
50 // It is a write-only usage, since we still allow usages on the right for reads
51 if (parentStatement.left !== privateIdentifierNode.parent) {
52 return false;
53 }
54
55 // For any other operator (such as '+=') we still consider it a read operation
56 if (isAssignmentExpression && parentStatement.operator !== "=") {
57
58 /*
59 * However, if the read operation is "discarded" in an empty statement, then
60 * we consider it write only.
61 */
62 return parentStatement.parent.type === "ExpressionStatement";
63 }
64
65 return true;
66 }
67
68 //--------------------------------------------------------------------------
69 // Public
70 //--------------------------------------------------------------------------
71
72 return {
73
74 // Collect all declared members up front and assume they are all unused
75 ClassBody(classBodyNode) {
76 const privateMembers = new Map();
77
78 trackedClasses.unshift(privateMembers);
79 for (const bodyMember of classBodyNode.body) {
80 if (bodyMember.type === "PropertyDefinition" || bodyMember.type === "MethodDefinition") {
81 if (bodyMember.key.type === "PrivateIdentifier") {
82 privateMembers.set(bodyMember.key.name, {
83 declaredNode: bodyMember,
84 isAccessor: bodyMember.type === "MethodDefinition" &&
85 (bodyMember.kind === "set" || bodyMember.kind === "get")
86 });
87 }
88 }
89 }
90 },
91
92 /*
93 * Process all usages of the private identifier and remove a member from
94 * `declaredAndUnusedPrivateMembers` if we deem it used.
95 */
96 PrivateIdentifier(privateIdentifierNode) {
97 const classBody = trackedClasses.find(classProperties => classProperties.has(privateIdentifierNode.name));
98
99 // Can't happen, as it is a parser to have a missing class body, but let's code defensively here.
100 if (!classBody) {
101 return;
102 }
103
104 // In case any other usage was already detected, we can short circuit the logic here.
105 const memberDefinition = classBody.get(privateIdentifierNode.name);
106
107 if (memberDefinition.isUsed) {
108 return;
109 }
110
111 // The definition of the class member itself
112 if (privateIdentifierNode.parent.type === "PropertyDefinition" ||
113 privateIdentifierNode.parent.type === "MethodDefinition") {
114 return;
115 }
116
117 /*
118 * Any usage of an accessor is considered a read, as the getter/setter can have
119 * side-effects in its definition.
120 */
121 if (memberDefinition.isAccessor) {
122 memberDefinition.isUsed = true;
123 return;
124 }
125
126 // Any assignments to this member, except for assignments that also read
127 if (isWriteOnlyAssignment(privateIdentifierNode)) {
128 return;
129 }
130
131 const wrappingExpressionType = privateIdentifierNode.parent.parent.type;
132 const parentOfWrappingExpressionType = privateIdentifierNode.parent.parent.parent.type;
133
134 // A statement which only increments (`this.#x++;`)
135 if (wrappingExpressionType === "UpdateExpression" &&
136 parentOfWrappingExpressionType === "ExpressionStatement") {
137 return;
138 }
139
140 /*
141 * ({ x: this.#usedInDestructuring } = bar);
142 *
143 * But should treat the following as a read:
144 * ({ [this.#x]: a } = foo);
145 */
146 if (wrappingExpressionType === "Property" &&
147 parentOfWrappingExpressionType === "ObjectPattern" &&
148 privateIdentifierNode.parent.parent.value === privateIdentifierNode.parent) {
149 return;
150 }
151
152 // [...this.#unusedInRestPattern] = bar;
153 if (wrappingExpressionType === "RestElement") {
154 return;
155 }
156
157 // [this.#unusedInAssignmentPattern] = bar;
158 if (wrappingExpressionType === "ArrayPattern") {
159 return;
160 }
161
162 /*
163 * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used.
164 * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete
165 * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class
166 * as used, which is incorrect.
167 */
168 memberDefinition.isUsed = true;
169 },
170
171 /*
172 * Post-process the class members and report any remaining members.
173 * Since private members can only be accessed in the current class context,
174 * we can safely assume that all usages are within the current class body.
175 */
176 "ClassBody:exit"() {
177 const unusedPrivateMembers = trackedClasses.shift();
178
179 for (const [classMemberName, { declaredNode, isUsed }] of unusedPrivateMembers.entries()) {
180 if (isUsed) {
181 continue;
182 }
183 context.report({
184 node: declaredNode,
185 loc: declaredNode.key.loc,
186 messageId: "unusedPrivateClassMember",
187 data: {
188 classMemberName: `#${classMemberName}`
189 }
190 });
191 }
192 }
193 };
194 }
195};
Note: See TracBrowser for help on using the repository browser.