UNPKG

3.5 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 },
19 schema: [{
20 type: "object",
21 properties: {
22 exceptMethods: {
23 type: "array",
24 items: {
25 type: "string"
26 }
27 }
28 },
29 additionalProperties: false
30 }]
31 },
32 create(context) {
33 const config = context.options[0] ? Object.assign({}, context.options[0]) : {};
34 const exceptMethods = new Set(config.exceptMethods || []);
35
36 const stack = [];
37
38 /**
39 * Initializes the current context to false and pushes it onto the stack.
40 * These booleans represent whether 'this' has been used in the context.
41 * @returns {void}
42 * @private
43 */
44 function enterFunction() {
45 stack.push(false);
46 }
47
48 /**
49 * Check if the node is an instance method
50 * @param {ASTNode} node - node to check
51 * @returns {boolean} True if its an instance method
52 * @private
53 */
54 function isInstanceMethod(node) {
55 return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
56 }
57
58 /**
59 * Check if the node is an instance method not excluded by config
60 * @param {ASTNode} node - node to check
61 * @returns {boolean} True if it is an instance method, and not excluded by config
62 * @private
63 */
64 function isIncludedInstanceMethod(node) {
65 return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
66 }
67
68 /**
69 * Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
70 * Static methods and the constructor are exempt.
71 * Then pops the context off the stack.
72 * @param {ASTNode} node - A function node that was entered.
73 * @returns {void}
74 * @private
75 */
76 function exitFunction(node) {
77 const methodUsesThis = stack.pop();
78
79 if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
80 context.report({
81 node,
82 message: "Expected 'this' to be used by class method '{{classMethod}}'.",
83 data: {
84 classMethod: node.parent.key.name
85 }
86 });
87 }
88 }
89
90 /**
91 * Mark the current context as having used 'this'.
92 * @returns {void}
93 * @private
94 */
95 function markThisUsed() {
96 if (stack.length) {
97 stack[stack.length - 1] = true;
98 }
99 }
100
101 return {
102 FunctionDeclaration: enterFunction,
103 "FunctionDeclaration:exit": exitFunction,
104 FunctionExpression: enterFunction,
105 "FunctionExpression:exit": exitFunction,
106 ThisExpression: markThisUsed,
107 Super: markThisUsed
108 };
109 }
110};