UNPKG

3.9 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// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19 meta: {
20 type: "suggestion",
21
22 docs: {
23 description: "enforce that class methods utilize `this`",
24 category: "Best Practices",
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 }
37 }
38 },
39 additionalProperties: false
40 }],
41
42 messages: {
43 missingThis: "Expected 'this' to be used by class {{name}}."
44 }
45 },
46 create(context) {
47 const config = Object.assign({}, context.options[0]);
48 const exceptMethods = new Set(config.exceptMethods || []);
49
50 const stack = [];
51
52 /**
53 * Initializes the current context to false and pushes it onto the stack.
54 * These booleans represent whether 'this' has been used in the context.
55 * @returns {void}
56 * @private
57 */
58 function enterFunction() {
59 stack.push(false);
60 }
61
62 /**
63 * Check if the node is an instance method
64 * @param {ASTNode} node node to check
65 * @returns {boolean} True if its an instance method
66 * @private
67 */
68 function isInstanceMethod(node) {
69 return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
70 }
71
72 /**
73 * Check if the node is an instance method not excluded by config
74 * @param {ASTNode} node node to check
75 * @returns {boolean} True if it is an instance method, and not excluded by config
76 * @private
77 */
78 function isIncludedInstanceMethod(node) {
79 return isInstanceMethod(node) &&
80 (node.computed || !exceptMethods.has(node.key.name));
81 }
82
83 /**
84 * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
85 * Static methods and the constructor are exempt.
86 * Then pops the context off the stack.
87 * @param {ASTNode} node A function node that was entered.
88 * @returns {void}
89 * @private
90 */
91 function exitFunction(node) {
92 const methodUsesThis = stack.pop();
93
94 if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
95 context.report({
96 node,
97 messageId: "missingThis",
98 data: {
99 name: astUtils.getFunctionNameWithKind(node)
100 }
101 });
102 }
103 }
104
105 /**
106 * Mark the current context as having used 'this'.
107 * @returns {void}
108 * @private
109 */
110 function markThisUsed() {
111 if (stack.length) {
112 stack[stack.length - 1] = true;
113 }
114 }
115
116 return {
117 FunctionDeclaration: enterFunction,
118 "FunctionDeclaration:exit": exitFunction,
119 FunctionExpression: enterFunction,
120 "FunctionExpression:exit": exitFunction,
121 ThisExpression: markThisUsed,
122 Super: markThisUsed
123 };
124 }
125};