UNPKG

4.32 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag unnecessary bind calls
3 * @author Bence Dányi <bence@danyi.me>
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const getPropertyName = require("../ast-utils").getStaticPropertyName;
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = {
18 meta: {
19 docs: {
20 description: "disallow unnecessary calls to `.bind()`",
21 category: "Best Practices",
22 recommended: false
23 },
24
25 schema: []
26 },
27
28 create(context) {
29 let scopeInfo = null;
30
31 /**
32 * Reports a given function node.
33 *
34 * @param {ASTNode} node - A node to report. This is a FunctionExpression or
35 * an ArrowFunctionExpression.
36 * @returns {void}
37 */
38 function report(node) {
39 context.report({
40 node: node.parent.parent,
41 message: "The function binding is unnecessary.",
42 loc: node.parent.property.loc.start
43 });
44 }
45
46 /**
47 * Checks whether or not a given function node is the callee of `.bind()`
48 * method.
49 *
50 * e.g. `(function() {}.bind(foo))`
51 *
52 * @param {ASTNode} node - A node to report. This is a FunctionExpression or
53 * an ArrowFunctionExpression.
54 * @returns {boolean} `true` if the node is the callee of `.bind()` method.
55 */
56 function isCalleeOfBindMethod(node) {
57 const parent = node.parent;
58 const grandparent = parent.parent;
59
60 return (
61 grandparent &&
62 grandparent.type === "CallExpression" &&
63 grandparent.callee === parent &&
64 grandparent.arguments.length === 1 &&
65 parent.type === "MemberExpression" &&
66 parent.object === node &&
67 getPropertyName(parent) === "bind"
68 );
69 }
70
71 /**
72 * Adds a scope information object to the stack.
73 *
74 * @param {ASTNode} node - A node to add. This node is a FunctionExpression
75 * or a FunctionDeclaration node.
76 * @returns {void}
77 */
78 function enterFunction(node) {
79 scopeInfo = {
80 isBound: isCalleeOfBindMethod(node),
81 thisFound: false,
82 upper: scopeInfo
83 };
84 }
85
86 /**
87 * Removes the scope information object from the top of the stack.
88 * At the same time, this reports the function node if the function has
89 * `.bind()` and the `this` keywords found.
90 *
91 * @param {ASTNode} node - A node to remove. This node is a
92 * FunctionExpression or a FunctionDeclaration node.
93 * @returns {void}
94 */
95 function exitFunction(node) {
96 if (scopeInfo.isBound && !scopeInfo.thisFound) {
97 report(node);
98 }
99
100 scopeInfo = scopeInfo.upper;
101 }
102
103 /**
104 * Reports a given arrow function if the function is callee of `.bind()`
105 * method.
106 *
107 * @param {ASTNode} node - A node to report. This node is an
108 * ArrowFunctionExpression.
109 * @returns {void}
110 */
111 function exitArrowFunction(node) {
112 if (isCalleeOfBindMethod(node)) {
113 report(node);
114 }
115 }
116
117 /**
118 * Set the mark as the `this` keyword was found in this scope.
119 *
120 * @returns {void}
121 */
122 function markAsThisFound() {
123 if (scopeInfo) {
124 scopeInfo.thisFound = true;
125 }
126 }
127
128 return {
129 "ArrowFunctionExpression:exit": exitArrowFunction,
130 FunctionDeclaration: enterFunction,
131 "FunctionDeclaration:exit": exitFunction,
132 FunctionExpression: enterFunction,
133 "FunctionExpression:exit": exitFunction,
134 ThisExpression: markAsThisFound
135 };
136 }
137};