]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/class-methods-use-this.js
beb742d93a793824b88ef23f3299c06adaff680b
[pve-eslint.git] / eslint / lib / rules / class-methods-use-this.js
1 /**
2 * @fileoverview Rule to enforce that all class methods use 'this'.
3 * @author Patrick Williams
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Rule Definition
16 //------------------------------------------------------------------------------
17
18 module.exports = {
19 meta: {
20 type: "suggestion",
21
22 docs: {
23 description: "enforce that class methods utilize `this`",
24 recommended: false,
25 url: "https://eslint.org/docs/rules/class-methods-use-this"
26 },
27
28 schema: [{
29 type: "object",
30 properties: {
31 exceptMethods: {
32 type: "array",
33 items: {
34 type: "string"
35 }
36 },
37 enforceForClassFields: {
38 type: "boolean",
39 default: true
40 }
41 },
42 additionalProperties: false
43 }],
44
45 messages: {
46 missingThis: "Expected 'this' to be used by class {{name}}."
47 }
48 },
49 create(context) {
50 const config = Object.assign({}, context.options[0]);
51 const enforceForClassFields = config.enforceForClassFields !== false;
52 const exceptMethods = new Set(config.exceptMethods || []);
53
54 const stack = [];
55
56 /**
57 * Push `this` used flag initialized with `false` onto the stack.
58 * @returns {void}
59 */
60 function pushContext() {
61 stack.push(false);
62 }
63
64 /**
65 * Pop `this` used flag from the stack.
66 * @returns {boolean | undefined} `this` used flag
67 */
68 function popContext() {
69 return stack.pop();
70 }
71
72 /**
73 * Initializes the current context to false and pushes it onto the stack.
74 * These booleans represent whether 'this' has been used in the context.
75 * @returns {void}
76 * @private
77 */
78 function enterFunction() {
79 pushContext();
80 }
81
82 /**
83 * Check if the node is an instance method
84 * @param {ASTNode} node node to check
85 * @returns {boolean} True if its an instance method
86 * @private
87 */
88 function isInstanceMethod(node) {
89 switch (node.type) {
90 case "MethodDefinition":
91 return !node.static && node.kind !== "constructor";
92 case "PropertyDefinition":
93 return !node.static && enforceForClassFields;
94 default:
95 return false;
96 }
97 }
98
99 /**
100 * Check if the node is an instance method not excluded by config
101 * @param {ASTNode} node node to check
102 * @returns {boolean} True if it is an instance method, and not excluded by config
103 * @private
104 */
105 function isIncludedInstanceMethod(node) {
106 if (isInstanceMethod(node)) {
107 if (node.computed) {
108 return true;
109 }
110
111 const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
112 const name = node.key.type === "Literal"
113 ? astUtils.getStaticStringValue(node.key)
114 : (node.key.name || "");
115
116 return !exceptMethods.has(hashIfNeeded + name);
117 }
118 return false;
119 }
120
121 /**
122 * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
123 * Static methods and the constructor are exempt.
124 * Then pops the context off the stack.
125 * @param {ASTNode} node A function node that was entered.
126 * @returns {void}
127 * @private
128 */
129 function exitFunction(node) {
130 const methodUsesThis = popContext();
131
132 if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
133 context.report({
134 node,
135 loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()),
136 messageId: "missingThis",
137 data: {
138 name: astUtils.getFunctionNameWithKind(node)
139 }
140 });
141 }
142 }
143
144 /**
145 * Mark the current context as having used 'this'.
146 * @returns {void}
147 * @private
148 */
149 function markThisUsed() {
150 if (stack.length) {
151 stack[stack.length - 1] = true;
152 }
153 }
154
155 return {
156 FunctionDeclaration: enterFunction,
157 "FunctionDeclaration:exit": exitFunction,
158 FunctionExpression: enterFunction,
159 "FunctionExpression:exit": exitFunction,
160
161 /*
162 * Class field value are implicit functions.
163 */
164 "PropertyDefinition > *.key:exit": pushContext,
165 "PropertyDefinition:exit": popContext,
166
167 /*
168 * Class static blocks are implicit functions. They aren't required to use `this`,
169 * but we have to push context so that it captures any use of `this` in the static block
170 * separately from enclosing contexts, because static blocks have their own `this` and it
171 * shouldn't count as used `this` in enclosing contexts.
172 */
173 StaticBlock: pushContext,
174 "StaticBlock:exit": popContext,
175
176 ThisExpression: markThisUsed,
177 Super: markThisUsed,
178 ...(
179 enforceForClassFields && {
180 "PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
181 "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
182 }
183 )
184 };
185 }
186 };