UNPKG

3.63 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to enforce that all class methods use 'this'.
3 * @author Patrick Williams
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "enforce that class methods utilize `this`",
16 category: "Best Practices",
17 recommended: false,
18 url: "https://eslint.org/docs/rules/class-methods-use-this"
19 },
20 schema: [{
21 type: "object",
22 properties: {
23 exceptMethods: {
24 type: "array",
25 items: {
26 type: "string"
27 }
28 }
29 },
30 additionalProperties: false
31 }],
32
33 messages: {
34 missingThis: "Expected 'this' to be used by class method '{{name}}'."
35 }
36 },
37 create(context) {
38 const config = context.options[0] ? Object.assign({}, context.options[0]) : {};
39 const exceptMethods = new Set(config.exceptMethods || []);
40
41 const stack = [];
42
43 /**
44 * Initializes the current context to false and pushes it onto the stack.
45 * These booleans represent whether 'this' has been used in the context.
46 * @returns {void}
47 * @private
48 */
49 function enterFunction() {
50 stack.push(false);
51 }
52
53 /**
54 * Check if the node is an instance method
55 * @param {ASTNode} node - node to check
56 * @returns {boolean} True if its an instance method
57 * @private
58 */
59 function isInstanceMethod(node) {
60 return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
61 }
62
63 /**
64 * Check if the node is an instance method not excluded by config
65 * @param {ASTNode} node - node to check
66 * @returns {boolean} True if it is an instance method, and not excluded by config
67 * @private
68 */
69 function isIncludedInstanceMethod(node) {
70 return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
71 }
72
73 /**
74 * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
75 * Static methods and the constructor are exempt.
76 * Then pops the context off the stack.
77 * @param {ASTNode} node - A function node that was entered.
78 * @returns {void}
79 * @private
80 */
81 function exitFunction(node) {
82 const methodUsesThis = stack.pop();
83
84 if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
85 context.report({
86 node,
87 messageId: "missingThis",
88 data: {
89 name: node.parent.key.name
90 }
91 });
92 }
93 }
94
95 /**
96 * Mark the current context as having used 'this'.
97 * @returns {void}
98 * @private
99 */
100 function markThisUsed() {
101 if (stack.length) {
102 stack[stack.length - 1] = true;
103 }
104 }
105
106 return {
107 FunctionDeclaration: enterFunction,
108 "FunctionDeclaration:exit": exitFunction,
109 FunctionExpression: enterFunction,
110 "FunctionExpression:exit": exitFunction,
111 ThisExpression: markThisUsed,
112 Super: markThisUsed
113 };
114 }
115};