minor adjustment to readme
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-extra-bind.js
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
11 const astUtils = require("./utils/ast-utils");
12
13 //------------------------------------------------------------------------------
14 // Helpers
15 //------------------------------------------------------------------------------
16
17 const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]);
18
19 //------------------------------------------------------------------------------
20 // Rule Definition
21 //------------------------------------------------------------------------------
22
23 module.exports = {
24     meta: {
25         type: "suggestion",
26
27         docs: {
28             description: "disallow unnecessary calls to `.bind()`",
29             category: "Best Practices",
30             recommended: false,
31             url: "https://eslint.org/docs/rules/no-extra-bind"
32         },
33
34         schema: [],
35         fixable: "code",
36
37         messages: {
38             unexpected: "The function binding is unnecessary."
39         }
40     },
41
42     create(context) {
43         const sourceCode = context.getSourceCode();
44         let scopeInfo = null;
45
46         /**
47          * Checks if a node is free of side effects.
48          *
49          * This check is stricter than it needs to be, in order to keep the implementation simple.
50          * @param {ASTNode} node A node to check.
51          * @returns {boolean} True if the node is known to be side-effect free, false otherwise.
52          */
53         function isSideEffectFree(node) {
54             return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type);
55         }
56
57         /**
58          * Reports a given function node.
59          * @param {ASTNode} node A node to report. This is a FunctionExpression or
60          *      an ArrowFunctionExpression.
61          * @returns {void}
62          */
63         function report(node) {
64             context.report({
65                 node: node.parent.parent,
66                 messageId: "unexpected",
67                 loc: node.parent.property.loc.start,
68                 fix(fixer) {
69                     if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
70                         return null;
71                     }
72
73                     const firstTokenToRemove = sourceCode
74                         .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
75                     const lastTokenToRemove = sourceCode.getLastToken(node.parent.parent);
76
77                     if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
78                         return null;
79                     }
80
81                     return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
82                 }
83             });
84         }
85
86         /**
87          * Checks whether or not a given function node is the callee of `.bind()`
88          * method.
89          *
90          * e.g. `(function() {}.bind(foo))`
91          * @param {ASTNode} node A node to report. This is a FunctionExpression or
92          *      an ArrowFunctionExpression.
93          * @returns {boolean} `true` if the node is the callee of `.bind()` method.
94          */
95         function isCalleeOfBindMethod(node) {
96             const parent = node.parent;
97             const grandparent = parent.parent;
98
99             return (
100                 grandparent &&
101                 grandparent.type === "CallExpression" &&
102                 grandparent.callee === parent &&
103                 grandparent.arguments.length === 1 &&
104                 grandparent.arguments[0].type !== "SpreadElement" &&
105                 parent.type === "MemberExpression" &&
106                 parent.object === node &&
107                 astUtils.getStaticPropertyName(parent) === "bind"
108             );
109         }
110
111         /**
112          * Adds a scope information object to the stack.
113          * @param {ASTNode} node A node to add. This node is a FunctionExpression
114          *      or a FunctionDeclaration node.
115          * @returns {void}
116          */
117         function enterFunction(node) {
118             scopeInfo = {
119                 isBound: isCalleeOfBindMethod(node),
120                 thisFound: false,
121                 upper: scopeInfo
122             };
123         }
124
125         /**
126          * Removes the scope information object from the top of the stack.
127          * At the same time, this reports the function node if the function has
128          * `.bind()` and the `this` keywords found.
129          * @param {ASTNode} node A node to remove. This node is a
130          *      FunctionExpression or a FunctionDeclaration node.
131          * @returns {void}
132          */
133         function exitFunction(node) {
134             if (scopeInfo.isBound && !scopeInfo.thisFound) {
135                 report(node);
136             }
137
138             scopeInfo = scopeInfo.upper;
139         }
140
141         /**
142          * Reports a given arrow function if the function is callee of `.bind()`
143          * method.
144          * @param {ASTNode} node A node to report. This node is an
145          *      ArrowFunctionExpression.
146          * @returns {void}
147          */
148         function exitArrowFunction(node) {
149             if (isCalleeOfBindMethod(node)) {
150                 report(node);
151             }
152         }
153
154         /**
155          * Set the mark as the `this` keyword was found in this scope.
156          * @returns {void}
157          */
158         function markAsThisFound() {
159             if (scopeInfo) {
160                 scopeInfo.thisFound = true;
161             }
162         }
163
164         return {
165             "ArrowFunctionExpression:exit": exitArrowFunction,
166             FunctionDeclaration: enterFunction,
167             "FunctionDeclaration:exit": exitFunction,
168             FunctionExpression: enterFunction,
169             "FunctionExpression:exit": exitFunction,
170             ThisExpression: markAsThisFound
171         };
172     }
173 };