Skip to content

Commit d5e0fda

Browse files
committed
[New] destructuring-assignment: add option destructAtParameter
1 parent 24bf594 commit d5e0fda

File tree

4 files changed

+133
-4
lines changed

4 files changed

+133
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Enable the rules that you would like to use.
123123
| | | [react/boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props |
124124
| | | [react/button-has-type](docs/rules/button-has-type.md) | Forbid "button" element without an explicit "type" attribute |
125125
| | | [react/default-props-match-prop-types](docs/rules/default-props-match-prop-types.md) | Enforce all defaultProps are defined and not "required" in propTypes. |
126-
| | | [react/destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context |
126+
| | 🔧 | [react/destructuring-assignment](docs/rules/destructuring-assignment.md) | Enforce consistent usage of destructuring assignment of props, state, and context |
127127
|| | [react/display-name](docs/rules/display-name.md) | Prevent missing displayName in a React component definition |
128128
| | | [react/forbid-component-props](docs/rules/forbid-component-props.md) | Forbid certain props on components |
129129
| | | [react/forbid-dom-props](docs/rules/forbid-dom-props.md) | Forbid certain props on DOM Nodes |

docs/rules/destructuring-assignment.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const Foo = class extends React.PureComponent {
9191

9292
```js
9393
...
94-
"react/destructuring-assignment": [<enabled>, "always", { "ignoreClassFields": <boolean> }]
94+
"react/destructuring-assignment": [<enabled>, "always", { "ignoreClassFields": <boolean>, "destructAtParameter": "always" | "ignore" }]
9595
...
9696
```
9797

@@ -104,3 +104,33 @@ class Foo extends React.PureComponent {
104104
bar = this.props.bar
105105
}
106106
```
107+
108+
### `destructAtParameter` (default: "ignore")
109+
110+
This option can be one of `always` or `ignore`. When configured with `always`, the rule will require props destructuring happens at function parameter.
111+
112+
Examples of **incorrect** code for `destructAtParameter: 'always'` :
113+
114+
```jsx
115+
function Foo(props) {
116+
const {a} = props;
117+
return <>{a}</>
118+
}
119+
```
120+
121+
Examples of **correct** code for `destructAtParameter: 'always'` :
122+
123+
```jsx
124+
function Foo({a}) {
125+
return <>{a}</>
126+
}
127+
```
128+
129+
```jsx
130+
// Ignores when props is used elsewhere
131+
function Foo(props) {
132+
const {a} = props;
133+
useProps(props);
134+
return <Goo a={a}/>
135+
}
136+
```

lib/rules/destructuring-assignment.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const messages = {
5050
noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument',
5151
noDestructAssignment: 'Must never use destructuring {{type}} assignment',
5252
useDestructAssignment: 'Must use destructuring {{type}} assignment',
53+
destructAtParameter: 'Must destruct props at function parameter.',
5354
};
5455

5556
module.exports = {
@@ -60,7 +61,7 @@ module.exports = {
6061
recommended: false,
6162
url: docsUrl('destructuring-assignment'),
6263
},
63-
64+
fixable: 'code',
6465
messages,
6566

6667
schema: [{
@@ -75,6 +76,13 @@ module.exports = {
7576
ignoreClassFields: {
7677
type: 'boolean',
7778
},
79+
destructAtParameter: {
80+
type: 'string',
81+
enum: [
82+
'always',
83+
'ignore',
84+
],
85+
},
7886
},
7987
additionalProperties: false,
8088
}],
@@ -83,6 +91,7 @@ module.exports = {
8391
create: Components.detect((context, components, utils) => {
8492
const configuration = context.options[0] || DEFAULT_OPTION;
8593
const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
94+
const destructAtParameter = (context.options[1] && context.options[1].destructAtParameter) || 'ignore';
8695
const sfcParams = createSFCParams();
8796

8897
/**
@@ -230,6 +239,35 @@ module.exports = {
230239
},
231240
});
232241
}
242+
243+
if (SFCComponent && destructuringSFC && configuration === 'always' && destructAtParameter === 'always'
244+
&& node.init.name === 'props') {
245+
const propsRefs = context.getScope().set.get('props') && context.getScope().set.get('props').references;
246+
if (!propsRefs) {
247+
return;
248+
}
249+
// Skip if props is used elsewhere
250+
if (propsRefs.length > 1) {
251+
return;
252+
}
253+
report(context, messages.destructAtParameter, 'destructAtParameter', {
254+
node,
255+
fix(fixer) {
256+
const param = SFCComponent.node.params[0];
257+
if (!param) {
258+
return;
259+
}
260+
const replaceRange = [
261+
param.range[0],
262+
param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
263+
];
264+
return [
265+
fixer.replaceTextRange(replaceRange, context.getSourceCode().getText(node.id)),
266+
fixer.remove(node.parent),
267+
];
268+
},
269+
});
270+
}
233271
},
234272
};
235273
}),

tests/lib/rules/destructuring-assignment.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,24 @@ ruleTester.run('destructuring-assignment', rule, {
338338
}
339339
`,
340340
},
341+
{
342+
code: `
343+
function Foo(props) {
344+
const {a} = props;
345+
return <Goo {...props}>{a}</Goo>;
346+
}
347+
`,
348+
options: ['always', { destructAtParameter: 'always' }],
349+
},
350+
{
351+
code: `
352+
function Foo(props) {
353+
const {a} = props;
354+
return <Goo f={() => props}>{a}</Goo>;
355+
}
356+
`,
357+
options: ['always', { destructAtParameter: 'always' }],
358+
},
341359
]),
342360

343361
invalid: parsers.all([
@@ -632,7 +650,7 @@ ruleTester.run('destructuring-assignment', rule, {
632650
633651
const TestComp = (props) => {
634652
props.onClick3102();
635-
653+
636654
return (
637655
<div
638656
onClick={(evt) => {
@@ -720,5 +738,48 @@ ruleTester.run('destructuring-assignment', rule, {
720738
},
721739
],
722740
},
741+
{
742+
code: `
743+
function Foo(props) {
744+
const {a} = props;
745+
return <p>{a}</p>;
746+
}
747+
`,
748+
options: ['always', { destructAtParameter: 'always' }],
749+
errors: [
750+
{
751+
messageId: 'destructAtParameter',
752+
line: 3,
753+
},
754+
],
755+
output: `
756+
function Foo({a}) {
757+
758+
return <p>{a}</p>;
759+
}
760+
`,
761+
},
762+
{
763+
code: `
764+
function Foo(props: FooProps) {
765+
const {a} = props;
766+
return <p>{a}</p>;
767+
}
768+
`,
769+
options: ['always', { destructAtParameter: 'always' }],
770+
errors: [
771+
{
772+
messageId: 'destructAtParameter',
773+
line: 3,
774+
},
775+
],
776+
output: `
777+
function Foo({a}: FooProps) {
778+
779+
return <p>{a}</p>;
780+
}
781+
`,
782+
features: ['ts', 'no-babel'],
783+
},
723784
]),
724785
});

0 commit comments

Comments
 (0)