UNPKG

4.82 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 astUtils = require("../ast-utils");
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 url: "https://eslint.org/docs/rules/no-extra-bind"
24 },
25
26 schema: [],
27
28 fixable: "code",
29
30 messages: {
31 unexpected: "The function binding is unnecessary."
32 }
33 },
34
35 create(context) {
36 let scopeInfo = null;
37
38 /**
39 * Reports a given function node.
40 *
41 * @param {ASTNode} node - A node to report. This is a FunctionExpression or
42 * an ArrowFunctionExpression.
43 * @returns {void}
44 */
45 function report(node) {
46 context.report({
47 node: node.parent.parent,
48 messageId: "unexpected",
49 loc: node.parent.property.loc.start,
50 fix(fixer) {
51 const firstTokenToRemove = context.getSourceCode()
52 .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
53
54 return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
55 }
56 });
57 }
58
59 /**
60 * Checks whether or not a given function node is the callee of `.bind()`
61 * method.
62 *
63 * e.g. `(function() {}.bind(foo))`
64 *
65 * @param {ASTNode} node - A node to report. This is a FunctionExpression or
66 * an ArrowFunctionExpression.
67 * @returns {boolean} `true` if the node is the callee of `.bind()` method.
68 */
69 function isCalleeOfBindMethod(node) {
70 const parent = node.parent;
71 const grandparent = parent.parent;
72
73 return (
74 grandparent &&
75 grandparent.type === "CallExpression" &&
76 grandparent.callee === parent &&
77 grandparent.arguments.length === 1 &&
78 parent.type === "MemberExpression" &&
79 parent.object === node &&
80 astUtils.getStaticPropertyName(parent) === "bind"
81 );
82 }
83
84 /**
85 * Adds a scope information object to the stack.
86 *
87 * @param {ASTNode} node - A node to add. This node is a FunctionExpression
88 * or a FunctionDeclaration node.
89 * @returns {void}
90 */
91 function enterFunction(node) {
92 scopeInfo = {
93 isBound: isCalleeOfBindMethod(node),
94 thisFound: false,
95 upper: scopeInfo
96 };
97 }
98
99 /**
100 * Removes the scope information object from the top of the stack.
101 * At the same time, this reports the function node if the function has
102 * `.bind()` and the `this` keywords found.
103 *
104 * @param {ASTNode} node - A node to remove. This node is a
105 * FunctionExpression or a FunctionDeclaration node.
106 * @returns {void}
107 */
108 function exitFunction(node) {
109 if (scopeInfo.isBound && !scopeInfo.thisFound) {
110 report(node);
111 }
112
113 scopeInfo = scopeInfo.upper;
114 }
115
116 /**
117 * Reports a given arrow function if the function is callee of `.bind()`
118 * method.
119 *
120 * @param {ASTNode} node - A node to report. This node is an
121 * ArrowFunctionExpression.
122 * @returns {void}
123 */
124 function exitArrowFunction(node) {
125 if (isCalleeOfBindMethod(node)) {
126 report(node);
127 }
128 }
129
130 /**
131 * Set the mark as the `this` keyword was found in this scope.
132 *
133 * @returns {void}
134 */
135 function markAsThisFound() {
136 if (scopeInfo) {
137 scopeInfo.thisFound = true;
138 }
139 }
140
141 return {
142 "ArrowFunctionExpression:exit": exitArrowFunction,
143 FunctionDeclaration: enterFunction,
144 "FunctionDeclaration:exit": exitFunction,
145 FunctionExpression: enterFunction,
146 "FunctionExpression:exit": exitFunction,
147 ThisExpression: markAsThisFound
148 };
149 }
150};