Skip to content

Commit

Permalink
[New] destructuring-assignment: add option destructAtParameter
Browse files Browse the repository at this point in the history
  • Loading branch information
golopot committed Mar 6, 2022
1 parent 24bf594 commit 5fb7b13
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 3 deletions.
32 changes: 31 additions & 1 deletion docs/rules/destructuring-assignment.md
Expand Up @@ -91,7 +91,7 @@ const Foo = class extends React.PureComponent {

```js
...
"react/destructuring-assignment": [<enabled>, "always", { "ignoreClassFields": <boolean> }]
"react/destructuring-assignment": [<enabled>, "always", { "ignoreClassFields": <boolean>, "destructAtParameter": "always" | "ignore" }]
...
```

Expand All @@ -104,3 +104,33 @@ class Foo extends React.PureComponent {
bar = this.props.bar
}
```

### `destructAtParameter` (default: "ignore")

This option can be one of `always` or `ignore`. When configured with `always`, the rule will require props destructuring happens at function parameter.

Examples of **incorrect** code for `destructAtParameter: 'always'` :

```jsx
function Foo(props) {
const {a} = props;
return <>{a}</>
}
```

Examples of **correct** code for `destructAtParameter: 'always'` :

```jsx
function Foo({a}) {
return <>{a}</>
}
```

```jsx
// Ignores when props is used elsewhere
function Foo(props) {
const {a} = props;
useProps(props);
return <Goo a={a}/>
}
```
40 changes: 39 additions & 1 deletion lib/rules/destructuring-assignment.js
Expand Up @@ -50,6 +50,7 @@ const messages = {
noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument',
noDestructAssignment: 'Must never use destructuring {{type}} assignment',
useDestructAssignment: 'Must use destructuring {{type}} assignment',
destructAtParameter: 'Must destruct props at function parameter.',
};

module.exports = {
Expand All @@ -60,7 +61,7 @@ module.exports = {
recommended: false,
url: docsUrl('destructuring-assignment'),
},

fixable: 'code',
messages,

schema: [{
Expand All @@ -75,6 +76,13 @@ module.exports = {
ignoreClassFields: {
type: 'boolean',
},
destructAtParameter: {
type: 'string',
enum: [
'always',
'ignore',
],
},
},
additionalProperties: false,
}],
Expand All @@ -83,6 +91,7 @@ module.exports = {
create: Components.detect((context, components, utils) => {
const configuration = context.options[0] || DEFAULT_OPTION;
const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
const destructAtParameter = (context.options[1] && context.options[1].destructAtParameter) || 'ignore';
const sfcParams = createSFCParams();

/**
Expand Down Expand Up @@ -230,6 +239,35 @@ module.exports = {
},
});
}

if (SFCComponent && destructuringSFC && configuration === 'always' && destructAtParameter === 'always'
&& node.init.name === 'props') {
const propsRefs = context.getScope().set.get('props') && context.getScope().set.get('props').references;
if (!propsRefs) {
return;
}
// Skip if props is used elsewhere
if (propsRefs.length > 1) {
return;
}
report(context, messages.destructAtParameter, 'destructAtParameter', {
node,
fix(fixer) {
const param = SFCComponent.node.params[0];
if (!param) {
return;
}
const replaceRange = [
param.range[0],
param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
];
return [
fixer.replaceTextRange(replaceRange, context.getSourceCode().getText(node.id)),
fixer.remove(node.parent),
];
},
});
}
},
};
}),
Expand Down
63 changes: 62 additions & 1 deletion tests/lib/rules/destructuring-assignment.js
Expand Up @@ -338,6 +338,24 @@ ruleTester.run('destructuring-assignment', rule, {
}
`,
},
{
code: `
function Foo(props) {
const {a} = props;
return <Goo {...props}>{a}</Goo>;
}
`,
options: ['always', { destructAtParameter: 'always' }],
},
{
code: `
function Foo(props) {
const {a} = props;
return <Goo f={() => props}>{a}</Goo>;
}
`,
options: ['always', { destructAtParameter: 'always' }],
},
]),

invalid: parsers.all([
Expand Down Expand Up @@ -632,7 +650,7 @@ ruleTester.run('destructuring-assignment', rule, {
const TestComp = (props) => {
props.onClick3102();
return (
<div
onClick={(evt) => {
Expand Down Expand Up @@ -720,5 +738,48 @@ ruleTester.run('destructuring-assignment', rule, {
},
],
},
{
code: `
function Foo(props) {
const {a} = props;
return <p>{a}</p>;
}
`,
options: ['always', { destructAtParameter: 'always' }],
errors: [
{
messageId: 'destructAtParameter',
line: 3,
},
],
output: `
function Foo({a}) {
return <p>{a}</p>;
}
`,
},
{
code: `
function Foo(props: FooProps) {
const {a} = props;
return <p>{a}</p>;
}
`,
options: ['always', { destructAtParameter: 'always' }],
errors: [
{
messageId: 'destructAtParameter',
line: 3,
},
],
output: `
function Foo({a}: FooProps) {
return <p>{a}</p>;
}
`,
features: ['ts', 'no-babel'],
},
]),
});

0 comments on commit 5fb7b13

Please sign in to comment.