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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 45 additions & 0 deletions
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

Lines changed: 99 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy