forward-ref-uses-ref.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. /**
  2. * @fileoverview Require all forwardRef components include a ref parameter
  3. */
  4. 'use strict';
  5. const isParenthesized = require('../util/ast').isParenthesized;
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. const getMessageData = require('../util/message');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. /**
  13. * @param {ASTNode} node
  14. * @returns {boolean} If the node represents the identifier `forwardRef`.
  15. */
  16. function isForwardRefIdentifier(node) {
  17. return node.type === 'Identifier' && node.name === 'forwardRef';
  18. }
  19. /**
  20. * @param {ASTNode} node
  21. * @returns {boolean} If the node represents a function call `forwardRef()` or `React.forwardRef()`.
  22. */
  23. function isForwardRefCall(node) {
  24. return (
  25. node.type === 'CallExpression'
  26. && (
  27. isForwardRefIdentifier(node.callee)
  28. || (node.callee.type === 'MemberExpression' && isForwardRefIdentifier(node.callee.property))
  29. )
  30. );
  31. }
  32. const messages = {
  33. missingRefParameter: 'forwardRef is used with this component but no ref parameter is set',
  34. addRefParameter: 'Add a ref parameter',
  35. removeForwardRef: 'Remove forwardRef wrapper',
  36. };
  37. /** @type {import('eslint').Rule.RuleModule} */
  38. module.exports = {
  39. meta: {
  40. docs: {
  41. description: 'Require all forwardRef components include a ref parameter',
  42. category: 'Possible Errors',
  43. recommended: false,
  44. url: docsUrl('forward-ref-uses-ref'),
  45. },
  46. messages,
  47. schema: [],
  48. type: 'suggestion',
  49. hasSuggestions: true,
  50. },
  51. create(context) {
  52. const sourceCode = context.getSourceCode();
  53. return {
  54. 'FunctionExpression, ArrowFunctionExpression'(node) {
  55. if (!isForwardRefCall(node.parent)) {
  56. return;
  57. }
  58. if (node.params.length === 1) {
  59. report(context, messages.missingRefParameter, 'missingRefParameter', {
  60. node,
  61. suggest: [
  62. Object.assign(
  63. getMessageData('addRefParameter', messages.addRefParameter),
  64. {
  65. fix(fixer) {
  66. const param = node.params[0];
  67. // If using shorthand arrow function syntax, add parentheses around the new parameter pair
  68. const shouldAddParentheses = node.type === 'ArrowFunctionExpression' && !isParenthesized(context, param);
  69. return [].concat(
  70. shouldAddParentheses ? fixer.insertTextBefore(param, '(') : [],
  71. fixer.insertTextAfter(param, `, ref${shouldAddParentheses ? ')' : ''}`)
  72. );
  73. },
  74. }
  75. ),
  76. Object.assign(
  77. getMessageData('removeForwardRef', messages.removeForwardRef),
  78. {
  79. fix(fixer) {
  80. return fixer.replaceText(node.parent, sourceCode.getText(node));
  81. },
  82. }
  83. ),
  84. ],
  85. });
  86. }
  87. },
  88. };
  89. },
  90. };