UNPKG

5.82 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("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19 meta: {
20 docs: {
21 description: "require parentheses around immediate `function` invocations",
22 category: "Best Practices",
23 recommended: false,
24 url: "https://eslint.org/docs/rules/wrap-iife"
25 },
26
27 schema: [
28 {
29 enum: ["outside", "inside", "any"]
30 },
31 {
32 type: "object",
33 properties: {
34 functionPrototypeMethods: {
35 type: "boolean"
36 }
37 },
38 additionalProperties: false
39 }
40 ],
41
42 fixable: "code"
43 },
44
45 create(context) {
46
47 const style = context.options[0] || "outside";
48 const includeFunctionPrototypeMethods = (context.options[1] && context.options[1].functionPrototypeMethods) || false;
49
50 const sourceCode = context.getSourceCode();
51
52 /**
53 * Check if the node is wrapped in ()
54 * @param {ASTNode} node node to evaluate
55 * @returns {boolean} True if it is wrapped
56 * @private
57 */
58 function wrapped(node) {
59 return astUtils.isParenthesised(sourceCode, node);
60 }
61
62 /**
63 * Get the function node from an IIFE
64 * @param {ASTNode} node node to evaluate
65 * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
66 */
67 function getFunctionNodeFromIIFE(node) {
68 const callee = node.callee;
69
70 if (callee.type === "FunctionExpression") {
71 return callee;
72 }
73
74 if (includeFunctionPrototypeMethods &&
75 callee.type === "MemberExpression" &&
76 callee.object.type === "FunctionExpression" &&
77 (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
78 ) {
79 return callee.object;
80 }
81
82 return null;
83 }
84
85
86 return {
87 CallExpression(node) {
88 const innerNode = getFunctionNodeFromIIFE(node);
89
90 if (!innerNode) {
91 return;
92 }
93
94 const callExpressionWrapped = wrapped(node),
95 functionExpressionWrapped = wrapped(innerNode);
96
97 if (!callExpressionWrapped && !functionExpressionWrapped) {
98 context.report({
99 node,
100 message: "Wrap an immediate function invocation in parentheses.",
101 fix(fixer) {
102 const nodeToSurround = style === "inside" ? innerNode : node;
103
104 return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
105 }
106 });
107 } else if (style === "inside" && !functionExpressionWrapped) {
108 context.report({
109 node,
110 message: "Wrap only the function expression in parens.",
111 fix(fixer) {
112
113 /*
114 * The outer call expression will always be wrapped at this point.
115 * Replace the range between the end of the function expression and the end of the call expression.
116 * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
117 * Replace the parens from the outer expression, and parenthesize the function expression.
118 */
119 const parenAfter = sourceCode.getTokenAfter(node);
120
121 return fixer.replaceTextRange(
122 [innerNode.range[1], parenAfter.range[1]],
123 `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
124 );
125 }
126 });
127 } else if (style === "outside" && !callExpressionWrapped) {
128 context.report({
129 node,
130 message: "Move the invocation into the parens that contain the function.",
131 fix(fixer) {
132
133 /*
134 * The inner function expression will always be wrapped at this point.
135 * It's only necessary to replace the range between the end of the function expression
136 * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
137 * should get replaced with `(bar))`.
138 */
139 const parenAfter = sourceCode.getTokenAfter(innerNode);
140
141 return fixer.replaceTextRange(
142 [parenAfter.range[0], node.range[1]],
143 `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
144 );
145 }
146 });
147 }
148 }
149 };
150
151 }
152};