wrap-iife.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /**
  2. * @fileoverview Rule to flag when IIFE is not wrapped in parens
  3. * @author Ilya Volodin
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. const eslintUtils = require("@eslint-community/eslint-utils");
  12. //----------------------------------------------------------------------
  13. // Helpers
  14. //----------------------------------------------------------------------
  15. /**
  16. * Check if the given node is callee of a `NewExpression` node
  17. * @param {ASTNode} node node to check
  18. * @returns {boolean} True if the node is callee of a `NewExpression` node
  19. * @private
  20. */
  21. function isCalleeOfNewExpression(node) {
  22. const maybeCallee =
  23. node.parent.type === "ChainExpression" ? node.parent : node;
  24. return (
  25. maybeCallee.parent.type === "NewExpression" &&
  26. maybeCallee.parent.callee === maybeCallee
  27. );
  28. }
  29. //------------------------------------------------------------------------------
  30. // Rule Definition
  31. //------------------------------------------------------------------------------
  32. /** @type {import('../types').Rule.RuleModule} */
  33. module.exports = {
  34. meta: {
  35. deprecated: {
  36. message: "Formatting rules are being moved out of ESLint core.",
  37. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  38. deprecatedSince: "8.53.0",
  39. availableUntil: "11.0.0",
  40. replacedBy: [
  41. {
  42. message:
  43. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  44. url: "https://eslint.style/guide/migration",
  45. plugin: {
  46. name: "@stylistic/eslint-plugin",
  47. url: "https://eslint.style",
  48. },
  49. rule: {
  50. name: "wrap-iife",
  51. url: "https://eslint.style/rules/wrap-iife",
  52. },
  53. },
  54. ],
  55. },
  56. type: "layout",
  57. docs: {
  58. description:
  59. "Require parentheses around immediate `function` invocations",
  60. recommended: false,
  61. url: "https://eslint.org/docs/latest/rules/wrap-iife",
  62. },
  63. schema: [
  64. {
  65. enum: ["outside", "inside", "any"],
  66. },
  67. {
  68. type: "object",
  69. properties: {
  70. functionPrototypeMethods: {
  71. type: "boolean",
  72. default: false,
  73. },
  74. },
  75. additionalProperties: false,
  76. },
  77. ],
  78. fixable: "code",
  79. messages: {
  80. wrapInvocation:
  81. "Wrap an immediate function invocation in parentheses.",
  82. wrapExpression: "Wrap only the function expression in parens.",
  83. moveInvocation:
  84. "Move the invocation into the parens that contain the function.",
  85. },
  86. },
  87. create(context) {
  88. const style = context.options[0] || "outside";
  89. const includeFunctionPrototypeMethods =
  90. context.options[1] && context.options[1].functionPrototypeMethods;
  91. const sourceCode = context.sourceCode;
  92. /**
  93. * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if()
  94. * @param {ASTNode} node node to evaluate
  95. * @returns {boolean} True if it is wrapped in any parens
  96. * @private
  97. */
  98. function isWrappedInAnyParens(node) {
  99. return astUtils.isParenthesised(sourceCode, node);
  100. }
  101. /**
  102. * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count
  103. * @param {ASTNode} node node to evaluate
  104. * @returns {boolean} True if it is wrapped in grouping parens
  105. * @private
  106. */
  107. function isWrappedInGroupingParens(node) {
  108. return eslintUtils.isParenthesized(1, node, sourceCode);
  109. }
  110. /**
  111. * Get the function node from an IIFE
  112. * @param {ASTNode} node node to evaluate
  113. * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
  114. */
  115. function getFunctionNodeFromIIFE(node) {
  116. const callee = astUtils.skipChainExpression(node.callee);
  117. if (callee.type === "FunctionExpression") {
  118. return callee;
  119. }
  120. if (
  121. includeFunctionPrototypeMethods &&
  122. callee.type === "MemberExpression" &&
  123. callee.object.type === "FunctionExpression" &&
  124. (astUtils.getStaticPropertyName(callee) === "call" ||
  125. astUtils.getStaticPropertyName(callee) === "apply")
  126. ) {
  127. return callee.object;
  128. }
  129. return null;
  130. }
  131. return {
  132. CallExpression(node) {
  133. const innerNode = getFunctionNodeFromIIFE(node);
  134. if (!innerNode) {
  135. return;
  136. }
  137. const isCallExpressionWrapped = isWrappedInAnyParens(node),
  138. isFunctionExpressionWrapped =
  139. isWrappedInAnyParens(innerNode);
  140. if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) {
  141. context.report({
  142. node,
  143. messageId: "wrapInvocation",
  144. fix(fixer) {
  145. const nodeToSurround =
  146. style === "inside" ? innerNode : node;
  147. return fixer.replaceText(
  148. nodeToSurround,
  149. `(${sourceCode.getText(nodeToSurround)})`,
  150. );
  151. },
  152. });
  153. } else if (style === "inside" && !isFunctionExpressionWrapped) {
  154. context.report({
  155. node,
  156. messageId: "wrapExpression",
  157. fix(fixer) {
  158. // The outer call expression will always be wrapped at this point.
  159. if (
  160. isWrappedInGroupingParens(node) &&
  161. !isCalleeOfNewExpression(node)
  162. ) {
  163. /*
  164. * Parenthesize the function expression and remove unnecessary grouping parens around the call expression.
  165. * Replace the range between the end of the function expression and the end of the call expression.
  166. * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`.
  167. */
  168. const parenAfter =
  169. sourceCode.getTokenAfter(node);
  170. return fixer.replaceTextRange(
  171. [innerNode.range[1], parenAfter.range[1]],
  172. `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`,
  173. );
  174. }
  175. /*
  176. * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens.
  177. * These parens cannot be removed, so just parenthesize the function expression.
  178. */
  179. return fixer.replaceText(
  180. innerNode,
  181. `(${sourceCode.getText(innerNode)})`,
  182. );
  183. },
  184. });
  185. } else if (style === "outside" && !isCallExpressionWrapped) {
  186. context.report({
  187. node,
  188. messageId: "moveInvocation",
  189. fix(fixer) {
  190. /*
  191. * The inner function expression will always be wrapped at this point.
  192. * It's only necessary to replace the range between the end of the function expression
  193. * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)`
  194. * should get replaced with `(bar))`.
  195. */
  196. const parenAfter =
  197. sourceCode.getTokenAfter(innerNode);
  198. return fixer.replaceTextRange(
  199. [parenAfter.range[0], node.range[1]],
  200. `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`,
  201. );
  202. },
  203. });
  204. }
  205. },
  206. };
  207. },
  208. };