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