.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / wrap-iife.js
1 /**
2  * @fileoverview Rule to flag when IIFE is not wrapped in parens
3  * @author Ilya Volodin
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13 const eslintUtils = require("eslint-utils");
14
15 //----------------------------------------------------------------------
16 // Helpers
17 //----------------------------------------------------------------------
18
19 /**
20  * Check if the given node is callee of a `NewExpression` node
21  * @param {ASTNode} node node to check
22  * @returns {boolean} True if the node is callee of a `NewExpression` node
23  * @private
24  */
25 function isCalleeOfNewExpression(node) {
26     const maybeCallee = node.parent.type === "ChainExpression"
27         ? node.parent
28         : node;
29
30     return (
31         maybeCallee.parent.type === "NewExpression" &&
32         maybeCallee.parent.callee === maybeCallee
33     );
34 }
35
36 //------------------------------------------------------------------------------
37 // Rule Definition
38 //------------------------------------------------------------------------------
39
40 module.exports = {
41     meta: {
42         type: "layout",
43
44         docs: {
45             description: "require parentheses around immediate `function` invocations",
46             category: "Best Practices",
47             recommended: false,
48             url: "https://eslint.org/docs/rules/wrap-iife"
49         },
50
51         schema: [
52             {
53                 enum: ["outside", "inside", "any"]
54             },
55             {
56                 type: "object",
57                 properties: {
58                     functionPrototypeMethods: {
59                         type: "boolean",
60                         default: false
61                     }
62                 },
63                 additionalProperties: false
64             }
65         ],
66
67         fixable: "code",
68         messages: {
69             wrapInvocation: "Wrap an immediate function invocation in parentheses.",
70             wrapExpression: "Wrap only the function expression in parens.",
71             moveInvocation: "Move the invocation into the parens that contain the function."
72         }
73     },
74
75     create(context) {
76
77         const style = context.options[0] || "outside";
78         const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods;
79
80         const sourceCode = context.getSourceCode();
81
82         /**
83          * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if()
84          * @param {ASTNode} node node to evaluate
85          * @returns {boolean} True if it is wrapped in any parens
86          * @private
87          */
88         function isWrappedInAnyParens(node) {
89             return astUtils.isParenthesised(sourceCode, node);
90         }
91
92         /**
93          * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count
94          * @param {ASTNode} node node to evaluate
95          * @returns {boolean} True if it is wrapped in grouping parens
96          * @private
97          */
98         function isWrappedInGroupingParens(node) {
99             return eslintUtils.isParenthesized(1, node, sourceCode);
100         }
101
102         /**
103          * Get the function node from an IIFE
104          * @param {ASTNode} node node to evaluate
105          * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
106          */
107         function getFunctionNodeFromIIFE(node) {
108             const callee = astUtils.skipChainExpression(node.callee);
109
110             if (callee.type === "FunctionExpression") {
111                 return callee;
112             }
113
114             if (includeFunctionPrototypeMethods &&
115                 callee.type === "MemberExpression" &&
116                 callee.object.type === "FunctionExpression" &&
117                 (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
118             ) {
119                 return callee.object;
120             }
121
122             return null;
123         }
124
125
126         return {
127             CallExpression(node) {
128                 const innerNode = getFunctionNodeFromIIFE(node);
129
130                 if (!innerNode) {
131                     return;
132                 }
133
134                 const isCallExpressionWrapped = isWrappedInAnyParens(node),
135                     isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode);
136
137                 if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) {
138                     context.report({
139                         node,
140                         messageId: "wrapInvocation",
141                         fix(fixer) {
142                             const nodeToSurround = style === "inside" ? innerNode : node;
143
144                             return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
145                         }
146                     });
147                 } else if (style === "inside" && !isFunctionExpressionWrapped) {
148                     context.report({
149                         node,
150                         messageId: "wrapExpression",
151                         fix(fixer) {
152
153                             // The outer call expression will always be wrapped at this point.
154
155                             if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) {
156
157                                 /*
158                                  * Parenthesize the function expression and remove unnecessary grouping parens around the call expression.
159                                  * Replace the range between the end of the function expression and the end of the call expression.
160                                  * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
161                                  */
162
163                                 const parenAfter = sourceCode.getTokenAfter(node);
164
165                                 return fixer.replaceTextRange(
166                                     [innerNode.range[1], parenAfter.range[1]],
167                                     `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
168                                 );
169                             }
170
171                             /*
172                              * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens.
173                              * These parens cannot be removed, so just parenthesize the function expression.
174                              */
175
176                             return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`);
177                         }
178                     });
179                 } else if (style === "outside" && !isCallExpressionWrapped) {
180                     context.report({
181                         node,
182                         messageId: "moveInvocation",
183                         fix(fixer) {
184
185                             /*
186                              * The inner function expression will always be wrapped at this point.
187                              * It's only necessary to replace the range between the end of the function expression
188                              * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
189                              * should get replaced with `(bar))`.
190                              */
191                             const parenAfter = sourceCode.getTokenAfter(innerNode);
192
193                             return fixer.replaceTextRange(
194                                 [parenAfter.range[0], node.range[1]],
195                                 `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
196                             );
197                         }
198                     });
199                 }
200             }
201         };
202
203     }
204 };