wrap-iife.js 5.7 KB

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