]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/wrap-iife.js
import 8.3.0 source
[pve-eslint.git] / 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 recommended: false,
47 url: "https://eslint.org/docs/rules/wrap-iife"
48 },
49
50 schema: [
51 {
52 enum: ["outside", "inside", "any"]
53 },
54 {
55 type: "object",
56 properties: {
57 functionPrototypeMethods: {
58 type: "boolean",
59 default: false
60 }
61 },
62 additionalProperties: false
63 }
64 ],
65
66 fixable: "code",
67 messages: {
68 wrapInvocation: "Wrap an immediate function invocation in parentheses.",
69 wrapExpression: "Wrap only the function expression in parens.",
70 moveInvocation: "Move the invocation into the parens that contain the function."
71 }
72 },
73
74 create(context) {
75
76 const style = context.options[0] || "outside";
77 const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods;
78
79 const sourceCode = context.getSourceCode();
80
81 /**
82 * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if()
83 * @param {ASTNode} node node to evaluate
84 * @returns {boolean} True if it is wrapped in any parens
85 * @private
86 */
87 function isWrappedInAnyParens(node) {
88 return astUtils.isParenthesised(sourceCode, node);
89 }
90
91 /**
92 * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count
93 * @param {ASTNode} node node to evaluate
94 * @returns {boolean} True if it is wrapped in grouping parens
95 * @private
96 */
97 function isWrappedInGroupingParens(node) {
98 return eslintUtils.isParenthesized(1, node, sourceCode);
99 }
100
101 /**
102 * Get the function node from an IIFE
103 * @param {ASTNode} node node to evaluate
104 * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
105 */
106 function getFunctionNodeFromIIFE(node) {
107 const callee = astUtils.skipChainExpression(node.callee);
108
109 if (callee.type === "FunctionExpression") {
110 return callee;
111 }
112
113 if (includeFunctionPrototypeMethods &&
114 callee.type === "MemberExpression" &&
115 callee.object.type === "FunctionExpression" &&
116 (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
117 ) {
118 return callee.object;
119 }
120
121 return null;
122 }
123
124
125 return {
126 CallExpression(node) {
127 const innerNode = getFunctionNodeFromIIFE(node);
128
129 if (!innerNode) {
130 return;
131 }
132
133 const isCallExpressionWrapped = isWrappedInAnyParens(node),
134 isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode);
135
136 if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) {
137 context.report({
138 node,
139 messageId: "wrapInvocation",
140 fix(fixer) {
141 const nodeToSurround = style === "inside" ? innerNode : node;
142
143 return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
144 }
145 });
146 } else if (style === "inside" && !isFunctionExpressionWrapped) {
147 context.report({
148 node,
149 messageId: "wrapExpression",
150 fix(fixer) {
151
152 // The outer call expression will always be wrapped at this point.
153
154 if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) {
155
156 /*
157 * Parenthesize the function expression and remove unnecessary grouping parens around the call expression.
158 * Replace the range between the end of the function expression and the end of the call expression.
159 * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
160 */
161
162 const parenAfter = sourceCode.getTokenAfter(node);
163
164 return fixer.replaceTextRange(
165 [innerNode.range[1], parenAfter.range[1]],
166 `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
167 );
168 }
169
170 /*
171 * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens.
172 * These parens cannot be removed, so just parenthesize the function expression.
173 */
174
175 return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`);
176 }
177 });
178 } else if (style === "outside" && !isCallExpressionWrapped) {
179 context.report({
180 node,
181 messageId: "moveInvocation",
182 fix(fixer) {
183
184 /*
185 * The inner function expression will always be wrapped at this point.
186 * It's only necessary to replace the range between the end of the function expression
187 * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
188 * should get replaced with `(bar))`.
189 */
190 const parenAfter = sourceCode.getTokenAfter(innerNode);
191
192 return fixer.replaceTextRange(
193 [parenAfter.range[0], node.range[1]],
194 `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
195 );
196 }
197 });
198 }
199 }
200 };
201
202 }
203 };