wrap-iife.js 5.9 KB

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