arrow-parens.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. * Get location should be reported by AST node.
  15. * @param {ASTNode} node AST Node.
  16. * @returns {Location} Location information.
  17. */
  18. function getLocation(node) {
  19. return {
  20. start: node.params[0].loc.start,
  21. end: node.params[node.params.length - 1].loc.end
  22. };
  23. }
  24. //------------------------------------------------------------------------------
  25. // Rule Definition
  26. //------------------------------------------------------------------------------
  27. module.exports = {
  28. meta: {
  29. type: "layout",
  30. docs: {
  31. description: "require parentheses around arrow function arguments",
  32. category: "ECMAScript 6",
  33. recommended: false,
  34. url: "https://eslint.org/docs/rules/arrow-parens"
  35. },
  36. fixable: "code",
  37. schema: [
  38. {
  39. enum: ["always", "as-needed"]
  40. },
  41. {
  42. type: "object",
  43. properties: {
  44. requireForBlockBody: {
  45. type: "boolean",
  46. default: false
  47. }
  48. },
  49. additionalProperties: false
  50. }
  51. ],
  52. messages: {
  53. unexpectedParens: "Unexpected parentheses around single function argument.",
  54. expectedParens: "Expected parentheses around arrow function argument.",
  55. unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
  56. expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
  57. }
  58. },
  59. create(context) {
  60. const asNeeded = context.options[0] === "as-needed";
  61. const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
  62. const sourceCode = context.getSourceCode();
  63. /**
  64. * Determines whether a arrow function argument end with `)`
  65. * @param {ASTNode} node The arrow function node.
  66. * @returns {void}
  67. */
  68. function parens(node) {
  69. const isAsync = node.async;
  70. const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
  71. /**
  72. * Remove the parenthesis around a parameter
  73. * @param {Fixer} fixer Fixer
  74. * @returns {string} fixed parameter
  75. */
  76. function fixParamsWithParenthesis(fixer) {
  77. const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
  78. /*
  79. * ES8 allows Trailing commas in function parameter lists and calls
  80. * https://github.com/eslint/eslint/issues/8834
  81. */
  82. const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
  83. const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
  84. const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]);
  85. return fixer.replaceTextRange([
  86. firstTokenOfParam.range[0],
  87. closingParenToken.range[1]
  88. ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
  89. }
  90. // "as-needed", { "requireForBlockBody": true }: x => x
  91. if (
  92. requireForBlockBody &&
  93. node.params.length === 1 &&
  94. node.params[0].type === "Identifier" &&
  95. !node.params[0].typeAnnotation &&
  96. node.body.type !== "BlockStatement" &&
  97. !node.returnType
  98. ) {
  99. if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
  100. context.report({
  101. node,
  102. messageId: "unexpectedParensInline",
  103. loc: getLocation(node),
  104. fix: fixParamsWithParenthesis
  105. });
  106. }
  107. return;
  108. }
  109. if (
  110. requireForBlockBody &&
  111. node.body.type === "BlockStatement"
  112. ) {
  113. if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
  114. context.report({
  115. node,
  116. messageId: "expectedParensBlock",
  117. loc: getLocation(node),
  118. fix(fixer) {
  119. return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
  120. }
  121. });
  122. }
  123. return;
  124. }
  125. // "as-needed": x => x
  126. if (asNeeded &&
  127. node.params.length === 1 &&
  128. node.params[0].type === "Identifier" &&
  129. !node.params[0].typeAnnotation &&
  130. !node.returnType
  131. ) {
  132. if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
  133. context.report({
  134. node,
  135. messageId: "unexpectedParens",
  136. loc: getLocation(node),
  137. fix: fixParamsWithParenthesis
  138. });
  139. }
  140. return;
  141. }
  142. if (firstTokenOfParam.type === "Identifier") {
  143. const after = sourceCode.getTokenAfter(firstTokenOfParam);
  144. // (x) => x
  145. if (after.value !== ")") {
  146. context.report({
  147. node,
  148. messageId: "expectedParens",
  149. loc: getLocation(node),
  150. fix(fixer) {
  151. return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
  152. }
  153. });
  154. }
  155. }
  156. }
  157. return {
  158. ArrowFunctionExpression: parens
  159. };
  160. }
  161. };