2 * @fileoverview Rule to flag when IIFE is not wrapped in parens
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
13 const eslintUtils
= require("eslint-utils");
15 //----------------------------------------------------------------------
17 //----------------------------------------------------------------------
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
25 function isCalleeOfNewExpression(node
) {
26 const maybeCallee
= node
.parent
.type
=== "ChainExpression"
31 maybeCallee
.parent
.type
=== "NewExpression" &&
32 maybeCallee
.parent
.callee
=== maybeCallee
36 //------------------------------------------------------------------------------
38 //------------------------------------------------------------------------------
45 description
: "require parentheses around immediate `function` invocations",
47 url
: "https://eslint.org/docs/rules/wrap-iife"
52 enum: ["outside", "inside", "any"]
57 functionPrototypeMethods
: {
62 additionalProperties
: false
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."
76 const style
= context
.options
[0] || "outside";
77 const includeFunctionPrototypeMethods
= context
.options
[1] && context
.options
[1].functionPrototypeMethods
;
79 const sourceCode
= context
.getSourceCode();
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
87 function isWrappedInAnyParens(node
) {
88 return astUtils
.isParenthesised(sourceCode
, node
);
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
97 function isWrappedInGroupingParens(node
) {
98 return eslintUtils
.isParenthesized(1, node
, sourceCode
);
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
106 function getFunctionNodeFromIIFE(node
) {
107 const callee
= astUtils
.skipChainExpression(node
.callee
);
109 if (callee
.type
=== "FunctionExpression") {
113 if (includeFunctionPrototypeMethods
&&
114 callee
.type
=== "MemberExpression" &&
115 callee
.object
.type
=== "FunctionExpression" &&
116 (astUtils
.getStaticPropertyName(callee
) === "call" || astUtils
.getStaticPropertyName(callee
) === "apply")
118 return callee
.object
;
126 CallExpression(node
) {
127 const innerNode
= getFunctionNodeFromIIFE(node
);
133 const isCallExpressionWrapped
= isWrappedInAnyParens(node
),
134 isFunctionExpressionWrapped
= isWrappedInAnyParens(innerNode
);
136 if (!isCallExpressionWrapped
&& !isFunctionExpressionWrapped
) {
139 messageId
: "wrapInvocation",
141 const nodeToSurround
= style
=== "inside" ? innerNode
: node
;
143 return fixer
.replaceText(nodeToSurround
, `(${sourceCode.getText(nodeToSurround)})`);
146 } else if (style
=== "inside" && !isFunctionExpressionWrapped
) {
149 messageId
: "wrapExpression",
152 // The outer call expression will always be wrapped at this point.
154 if (isWrappedInGroupingParens(node
) && !isCalleeOfNewExpression(node
)) {
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)`.
162 const parenAfter
= sourceCode
.getTokenAfter(node
);
164 return fixer
.replaceTextRange(
165 [innerNode
.range
[1], parenAfter
.range
[1]],
166 `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
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.
175 return fixer
.replaceText(innerNode
, `(${sourceCode.getText(innerNode)})`);
178 } else if (style
=== "outside" && !isCallExpressionWrapped
) {
181 messageId
: "moveInvocation",
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))`.
190 const parenAfter
= sourceCode
.getTokenAfter(innerNode
);
192 return fixer
.replaceTextRange(
193 [parenAfter
.range
[0], node
.range
[1]],
194 `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`