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}; 126