blob: 5e590be13e28b56579c9267b86bf7a6c83b92a78 [file] [log] [blame]
/**
* @fileoverview Rule to flag when IIFE is not wrapped in parens
* @author Ilya Volodin
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: "layout",
docs: {
description: "require parentheses around immediate `function` invocations",
category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/wrap-iife"
},
schema: [
{
enum: ["outside", "inside", "any"]
},
{
type: "object",
properties: {
functionPrototypeMethods: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
],
fixable: "code",
messages: {
wrapInvocation: "Wrap an immediate function invocation in parentheses.",
wrapExpression: "Wrap only the function expression in parens.",
moveInvocation: "Move the invocation into the parens that contain the function."
}
},
create(context) {
const style = context.options[0] || "outside";
const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods;
const sourceCode = context.getSourceCode();
/**
* Check if the node is wrapped in ()
* @param {ASTNode} node node to evaluate
* @returns {boolean} True if it is wrapped
* @private
*/
function wrapped(node) {
return astUtils.isParenthesised(sourceCode, node);
}
/**
* Get the function node from an IIFE
* @param {ASTNode} node node to evaluate
* @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
*/
function getFunctionNodeFromIIFE(node) {
const callee = node.callee;
if (callee.type === "FunctionExpression") {
return callee;
}
if (includeFunctionPrototypeMethods &&
callee.type === "MemberExpression" &&
callee.object.type === "FunctionExpression" &&
(astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply")
) {
return callee.object;
}
return null;
}
return {
CallExpression(node) {
const innerNode = getFunctionNodeFromIIFE(node);
if (!innerNode) {
return;
}
const callExpressionWrapped = wrapped(node),
functionExpressionWrapped = wrapped(innerNode);
if (!callExpressionWrapped && !functionExpressionWrapped) {
context.report({
node,
messageId: "wrapInvocation",
fix(fixer) {
const nodeToSurround = style === "inside" ? innerNode : node;
return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`);
}
});
} else if (style === "inside" && !functionExpressionWrapped) {
context.report({
node,
messageId: "wrapExpression",
fix(fixer) {
/*
* The outer call expression will always be wrapped at this point.
* Replace the range between the end of the function expression and the end of the call expression.
* for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
* Replace the parens from the outer expression, and parenthesize the function expression.
*/
const parenAfter = sourceCode.getTokenAfter(node);
return fixer.replaceTextRange(
[innerNode.range[1], parenAfter.range[1]],
`)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`
);
}
});
} else if (style === "outside" && !callExpressionWrapped) {
context.report({
node,
messageId: "moveInvocation",
fix(fixer) {
/*
* The inner function expression will always be wrapped at this point.
* It's only necessary to replace the range between the end of the function expression
* and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
* should get replaced with `(bar))`.
*/
const parenAfter = sourceCode.getTokenAfter(innerNode);
return fixer.replaceTextRange(
[parenAfter.range[0], node.range[1]],
`${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`
);
}
});
}
}
};
}
};