arrow-parens.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * @fileoverview Rule to require parens in arrow function arguments.
  3. * @author Jxck
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Determines if the given arrow function has block body.
  15. * @param {ASTNode} node `ArrowFunctionExpression` node.
  16. * @returns {boolean} `true` if the function has block body.
  17. */
  18. function hasBlockBody(node) {
  19. return node.body.type === "BlockStatement";
  20. }
  21. //------------------------------------------------------------------------------
  22. // Rule Definition
  23. //------------------------------------------------------------------------------
  24. /** @type {import('../shared/types').Rule} */
  25. module.exports = {
  26. meta: {
  27. type: "layout",
  28. docs: {
  29. description: "Require parentheses around arrow function arguments",
  30. recommended: false,
  31. url: "https://eslint.org/docs/rules/arrow-parens"
  32. },
  33. fixable: "code",
  34. schema: [
  35. {
  36. enum: ["always", "as-needed"]
  37. },
  38. {
  39. type: "object",
  40. properties: {
  41. requireForBlockBody: {
  42. type: "boolean",
  43. default: false
  44. }
  45. },
  46. additionalProperties: false
  47. }
  48. ],
  49. messages: {
  50. unexpectedParens: "Unexpected parentheses around single function argument.",
  51. expectedParens: "Expected parentheses around arrow function argument.",
  52. unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
  53. expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
  54. }
  55. },
  56. create(context) {
  57. const asNeeded = context.options[0] === "as-needed";
  58. const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
  59. const sourceCode = context.getSourceCode();
  60. /**
  61. * Finds opening paren of parameters for the given arrow function, if it exists.
  62. * It is assumed that the given arrow function has exactly one parameter.
  63. * @param {ASTNode} node `ArrowFunctionExpression` node.
  64. * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
  65. */
  66. function findOpeningParenOfParams(node) {
  67. const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
  68. if (
  69. tokenBeforeParams &&
  70. astUtils.isOpeningParenToken(tokenBeforeParams) &&
  71. node.range[0] <= tokenBeforeParams.range[0]
  72. ) {
  73. return tokenBeforeParams;
  74. }
  75. return null;
  76. }
  77. /**
  78. * Finds closing paren of parameters for the given arrow function.
  79. * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
  80. * @param {ASTNode} node `ArrowFunctionExpression` node.
  81. * @returns {Token} the closing paren of parameters.
  82. */
  83. function getClosingParenOfParams(node) {
  84. return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
  85. }
  86. /**
  87. * Determines whether the given arrow function has comments inside parens of parameters.
  88. * It is assumed that the given arrow function has parens of parameters.
  89. * @param {ASTNode} node `ArrowFunctionExpression` node.
  90. * @param {Token} openingParen Opening paren of parameters.
  91. * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
  92. */
  93. function hasCommentsInParensOfParams(node, openingParen) {
  94. return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
  95. }
  96. /**
  97. * Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
  98. * in which case it will be assumed that the existing parens of parameters are necessary.
  99. * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
  100. * Example: <T>(a) => b
  101. * @param {ASTNode} node `ArrowFunctionExpression` node.
  102. * @param {Token} openingParen Opening paren of parameters.
  103. * @returns {boolean} `true` if the function has at least one unexpected token.
  104. */
  105. function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
  106. const expectedCount = node.async ? 1 : 0;
  107. return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
  108. }
  109. return {
  110. "ArrowFunctionExpression[params.length=1]"(node) {
  111. const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
  112. const openingParen = findOpeningParenOfParams(node);
  113. const hasParens = openingParen !== null;
  114. const [param] = node.params;
  115. if (shouldHaveParens && !hasParens) {
  116. context.report({
  117. node,
  118. messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
  119. loc: param.loc,
  120. *fix(fixer) {
  121. yield fixer.insertTextBefore(param, "(");
  122. yield fixer.insertTextAfter(param, ")");
  123. }
  124. });
  125. }
  126. if (
  127. !shouldHaveParens &&
  128. hasParens &&
  129. param.type === "Identifier" &&
  130. !param.typeAnnotation &&
  131. !node.returnType &&
  132. !hasCommentsInParensOfParams(node, openingParen) &&
  133. !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
  134. ) {
  135. context.report({
  136. node,
  137. messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
  138. loc: param.loc,
  139. *fix(fixer) {
  140. const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
  141. const closingParen = getClosingParenOfParams(node);
  142. if (
  143. tokenBeforeOpeningParen &&
  144. tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
  145. !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
  146. ) {
  147. yield fixer.insertTextBefore(openingParen, " ");
  148. }
  149. // remove parens, whitespace inside parens, and possible trailing comma
  150. yield fixer.removeRange([openingParen.range[0], param.range[0]]);
  151. yield fixer.removeRange([param.range[1], closingParen.range[1]]);
  152. }
  153. });
  154. }
  155. }
  156. };
  157. }
  158. };