UNPKG

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