UNPKG

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