arrowParens.js.flow 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. const getLocation = (node) => ({
  2. end: node.params[node.params.length - 1].loc.end,
  3. start: node.params[0].loc.start,
  4. });
  5. const isOpeningParenToken = (token) => token.value === '(' && token.type === 'Punctuator';
  6. const isClosingParenToken = (token) => token.value === ')' && token.type === 'Punctuator';
  7. export default {
  8. create(context) {
  9. const asNeeded = context.options[0] === 'as-needed';
  10. const requireForBlockBody = (
  11. asNeeded
  12. && context.options[1] && context.options[1].requireForBlockBody === true
  13. );
  14. const sourceCode = context.getSourceCode();
  15. // Determines whether a arrow function argument end with `)`
  16. // eslint-disable-next-line complexity
  17. const parens = (node) => {
  18. const isAsync = node.async;
  19. const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
  20. // Remove the parenthesis around a parameter
  21. const fixParamsWithParenthesis = (fixer) => {
  22. const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
  23. /*
  24. * ES8 allows Trailing commas in function parameter lists and calls
  25. * https://github.com/eslint/eslint/issues/8834
  26. */
  27. const closingParenToken = sourceCode.getTokenAfter(paramToken, isClosingParenToken);
  28. const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
  29. const shouldAddSpaceForAsync = (
  30. asyncToken
  31. && asyncToken.range[1] === firstTokenOfParam.range[0]
  32. );
  33. return fixer.replaceTextRange([
  34. firstTokenOfParam.range[0],
  35. closingParenToken.range[1],
  36. ], `${shouldAddSpaceForAsync ? ' ' : ''}${paramToken.value}`);
  37. };
  38. // Type parameters without an opening paren is always a parse error, and
  39. // can therefore be safely ignored.
  40. if (node.typeParameters) {
  41. return;
  42. }
  43. // Similarly, a predicate always requires parens just like a return type
  44. // does, and therefore this case can also be safely ignored.
  45. if (node.predicate) {
  46. return;
  47. }
  48. // "as-needed", { "requireForBlockBody": true }: x => x
  49. if (
  50. requireForBlockBody
  51. && node.params.length === 1
  52. && node.params[0].type === 'Identifier'
  53. && !node.params[0].typeAnnotation
  54. && node.body.type !== 'BlockStatement'
  55. && !node.returnType
  56. ) {
  57. if (isOpeningParenToken(firstTokenOfParam)) {
  58. context.report({
  59. fix: fixParamsWithParenthesis,
  60. loc: getLocation(node),
  61. messageId: 'unexpectedParensInline',
  62. node,
  63. });
  64. }
  65. return;
  66. }
  67. if (
  68. requireForBlockBody
  69. && node.body.type === 'BlockStatement'
  70. ) {
  71. if (!isOpeningParenToken(firstTokenOfParam)) {
  72. context.report({
  73. fix(fixer) {
  74. return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
  75. },
  76. loc: getLocation(node),
  77. messageId: 'expectedParensBlock',
  78. node,
  79. });
  80. }
  81. return;
  82. }
  83. // "as-needed": x => x
  84. if (asNeeded
  85. && node.params.length === 1
  86. && node.params[0].type === 'Identifier'
  87. && !node.params[0].typeAnnotation
  88. && !node.returnType
  89. ) {
  90. if (isOpeningParenToken(firstTokenOfParam)) {
  91. context.report({
  92. fix: fixParamsWithParenthesis,
  93. loc: getLocation(node),
  94. messageId: 'unexpectedParens',
  95. node,
  96. });
  97. }
  98. return;
  99. }
  100. if (firstTokenOfParam.type === 'Identifier') {
  101. const after = sourceCode.getTokenAfter(firstTokenOfParam);
  102. // (x) => x
  103. if (after.value !== ')') {
  104. context.report({
  105. fix(fixer) {
  106. return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
  107. },
  108. loc: getLocation(node),
  109. messageId: 'expectedParens',
  110. node,
  111. });
  112. }
  113. }
  114. };
  115. return {
  116. ArrowFunctionExpression: parens,
  117. };
  118. },
  119. meta: {
  120. docs: {
  121. category: 'ECMAScript 6',
  122. description: 'require parentheses around arrow function arguments',
  123. recommended: false,
  124. url: 'https://eslint.org/docs/rules/arrow-parens',
  125. },
  126. fixable: 'code',
  127. messages: {
  128. expectedParens: 'Expected parentheses around arrow function argument.',
  129. expectedParensBlock: 'Expected parentheses around arrow function argument having a body with curly braces.',
  130. unexpectedParens: 'Unexpected parentheses around single function argument.',
  131. unexpectedParensInline: 'Unexpected parentheses around single function argument having a body with no curly braces.',
  132. },
  133. type: 'layout',
  134. },
  135. schema: [
  136. {
  137. enum: ['always', 'as-needed'],
  138. },
  139. {
  140. additionalProperties: false,
  141. properties: {
  142. requireForBlockBody: {
  143. default: false,
  144. type: 'boolean',
  145. },
  146. },
  147. type: 'object',
  148. },
  149. ],
  150. };