Skip to content

Commit ceb73fe

Browse files
NotWoodsljharb
authored andcommitted
[New] add forward-ref-uses-ref rule for checking ref parameter
1 parent ed64b24 commit ceb73fe

File tree

6 files changed

+413
-0
lines changed

6 files changed

+413
-0
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
99
### Added
1010
* [`no-string-refs`]: allow this.refs in > 18.3.0 ([#3807][] @henryqdineen)
1111
* [`jsx-no-literals`] Add `elementOverrides` option and the ability to ignore this rule on specific elements ([#3812][] @Pearce-Ropion)
12+
* [`forward-ref-uses-ref`]: add rule for checking ref parameter is added ([#3667][] @NotWoods)
1213

1314
### Fixed
1415
* [`function-component-definition`], [`boolean-prop-naming`], [`jsx-first-prop-new-line`], [`jsx-props-no-multi-spaces`], `propTypes`: use type args ([#3629][] @HenryBrown0)
@@ -27,6 +28,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
2728

2829
[#3812]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3812
2930
[#3731]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3731
31+
[#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3667
3032
[#3629]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3629
3133
[#3817]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3817
3234
[#3807]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3807

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ module.exports = [
301301
| [forbid-elements](docs/rules/forbid-elements.md) | Disallow certain elements | | | | | |
302302
| [forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Disallow using another component's propTypes | | | | | |
303303
| [forbid-prop-types](docs/rules/forbid-prop-types.md) | Disallow certain propTypes | | | | | |
304+
| [forward-ref-uses-ref](docs/rules/forward-ref-uses-ref.md) | Require all forwardRef components include a ref parameter | | | | 💡 | |
304305
| [function-component-definition](docs/rules/function-component-definition.md) | Enforce a specific function type for function components | | | 🔧 | | |
305306
| [hook-use-state](docs/rules/hook-use-state.md) | Ensure destructuring and symmetric naming of useState hook value and setter variables | | | | 💡 | |
306307
| [iframe-missing-sandbox](docs/rules/iframe-missing-sandbox.md) | Enforce sandbox attribute on iframe elements | | | | | |

docs/rules/forward-ref-uses-ref.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Require all forwardRef components include a ref parameter (`react/forward-ref-uses-ref`)
2+
3+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Requires that components wrapped with `forwardRef` must have a `ref` parameter. Omitting the `ref` argument is usually a bug, and components not using `ref` don't need to be wrapped by `forwardRef`.
8+
9+
See <https://react.dev/reference/react/forwardRef>
10+
11+
## Rule Details
12+
13+
This rule checks all React components using `forwardRef` and verifies that there is a second parameter.
14+
15+
The following patterns are considered warnings:
16+
17+
```jsx
18+
var React = require('react');
19+
20+
var Component = React.forwardRef((props) => (
21+
<div />
22+
));
23+
```
24+
25+
The following patterns are **not** considered warnings:
26+
27+
```jsx
28+
var React = require('react');
29+
30+
var Component = React.forwardRef((props, ref) => (
31+
<div ref={ref} />
32+
));
33+
34+
var Component = React.forwardRef((props, ref) => (
35+
<div />
36+
));
37+
38+
function Component(props) {
39+
return <div />;
40+
};
41+
```
42+
43+
## When not to use
44+
45+
If you don't want to enforce that components using `forwardRef` utilize the forwarded ref.

lib/rules/forward-ref-uses-ref.js

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

lib/rules/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'forbid-elements': require('./forbid-elements'),
1616
'forbid-foreign-prop-types': require('./forbid-foreign-prop-types'),
1717
'forbid-prop-types': require('./forbid-prop-types'),
18+
'forward-ref-uses-ref': require('./forward-ref-uses-ref'),
1819
'function-component-definition': require('./function-component-definition'),
1920
'hook-use-state': require('./hook-use-state'),
2021
'iframe-missing-sandbox': require('./iframe-missing-sandbox'),

0 commit comments

Comments
 (0)