UNPKG

7.88 kBJavaScriptView Raw
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
12const astUtils = require("./utils/ast-utils");
13const 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 */
25function 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
40module.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};