Skip to content

Commit a25003c

Browse files
committed
[destructuring-assignment] : new rule to enforce consistent usage of destructuring assignment
1 parent 1f14fad commit a25003c

File tree

5 files changed

+400
-0
lines changed

5 files changed

+400
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
8484

8585
* [react/boolean-prop-naming](docs/rules/boolean-prop-naming.md): Enforces consistent naming for boolean props
8686
* [react/default-props-match-prop-types](docs/rules/default-props-match-prop-types.md): Prevent extraneous defaultProps on components
87+
* [react/destructuring-assignment](docs/rules/destructuring-assignment.md): Rule enforces consistent usage of destructuring assignment in component
8788
* [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition
8889
* [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components
8990
* [react/forbid-elements](docs/rules/forbid-elements.md): Forbid certain elements
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Enforce consostent usage of destructuring assignment of props, state and context (react/destructuring-assignment)
2+
3+
Rule can be set to either of `always`, `never`, `ignore` for SFC and to `always` or `ignore` for class components.
4+
5+
```js
6+
"react/destructuring-assignment": [<enabled>, { "SFC": "always", "class": "always"}]
7+
```
8+
9+
## Rule Details
10+
11+
By default rule is set to `always` enforce destructuring assignment. The following patterns are considered warnings:
12+
13+
```js
14+
const MyComponent = (props) => {
15+
return (<div id={props.id} />)
16+
};
17+
```
18+
19+
```js
20+
const Foo = class extends React.PureComponent {
21+
render() {
22+
return <div>{this.context.foo}</div>;
23+
}
24+
};
25+
```
26+
27+
Below pattern is correct:
28+
29+
```js
30+
const MyComponent = ({id}) => {
31+
return (<div id={id} />)
32+
};
33+
```
34+
35+
```js
36+
const Foo = class extends React.PureComponent {
37+
render() {
38+
const { foo } = this.props;
39+
return <div>{foo}</div>;
40+
}
41+
};
42+
```
43+
44+
If rule option is set to `never` for SFC, the following pattern is considered warning:
45+
46+
```js
47+
const MyComponent = ({id}) => {
48+
return (<div id={id} />)
49+
};
50+
```
51+
52+
and below pattern is correct:
53+
54+
```js
55+
const MyComponent = (props) => {
56+
return (<div id={props.id} />)
57+
};
58+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const has = require('has');
55
const allRules = {
66
'boolean-prop-naming': require('./lib/rules/boolean-prop-naming'),
77
'default-props-match-prop-types': require('./lib/rules/default-props-match-prop-types'),
8+
'destructuring-assignment': require('./lib/rules/destructuring-assignment'),
89
'display-name': require('./lib/rules/display-name'),
910
'forbid-component-props': require('./lib/rules/forbid-component-props'),
1011
'forbid-elements': require('./lib/rules/forbid-elements'),

lib/rules/destructuring-assignment.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* @fileoverview Enforce consostent usage of destructuring assignment of props, state and context.
3+
**/
4+
'use strict';
5+
6+
const Components = require('../util/Components');
7+
8+
const DEFAULT_OPTIONS = {
9+
SFC: 'always',
10+
class: 'always'
11+
};
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Enforce consostent usage of destructuring assignment of props, state and context',
17+
category: 'Stylistic Issues',
18+
recommended: false
19+
},
20+
schema: [{
21+
definitions: {
22+
valueSFC: {
23+
enum: [
24+
'always',
25+
'never',
26+
'ignore'
27+
]
28+
},
29+
valueClass: {
30+
enum: [
31+
'always',
32+
'ignore'
33+
]
34+
}
35+
},
36+
type: 'object',
37+
properties: {
38+
SFC: {$ref: '#/definitions/valueSFC'},
39+
class: {$ref: '#/definitions/valueClass'}
40+
},
41+
additionalProperties: false
42+
}]
43+
},
44+
45+
create: Components.detect((context, components, utils) => {
46+
const configuration = context.options[0] || {};
47+
const options = {
48+
SFC: configuration.SFC || DEFAULT_OPTIONS.SFC,
49+
class: configuration.class || DEFAULT_OPTIONS.class
50+
};
51+
52+
53+
/**
54+
* Checks if a prop is being assigned a value props.bar = 'bar'
55+
* @param {ASTNode} node The AST node being checked.
56+
* @returns {Boolean}
57+
*/
58+
59+
function isAssignmentToProp(node) {
60+
return (
61+
node.parent &&
62+
node.parent.type === 'AssignmentExpression' &&
63+
node.parent.left === node
64+
);
65+
}
66+
/**
67+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
68+
* FunctionDeclaration, or FunctionExpression
69+
*/
70+
function handleStatelessComponent(node) {
71+
const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
72+
const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern';
73+
74+
if (destructuringProps && components.get(node) && options.SFC === 'never') {
75+
context.report({
76+
node: node,
77+
message: 'Must never use destructuring props assignment in SFC argument'
78+
});
79+
} else if (destructuringContext && components.get(node) && options.SFC === 'never') {
80+
context.report({
81+
node: node,
82+
message: 'Must never use destructuring context assignment in SFC argument'
83+
});
84+
}
85+
}
86+
87+
function handleSFCUsage(node) {
88+
// props.aProp || context.aProp
89+
const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentToProp(node);
90+
if (isPropUsed && options.SFC === 'always') {
91+
context.report({
92+
node: node,
93+
message: `Must use destructuring ${node.object.name} assignment`
94+
});
95+
}
96+
}
97+
98+
function handleClassUsage(node) {
99+
// this.props.Aprop || this.context.aProp || this.state.aState
100+
const isPropUsed = (node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression' &&
101+
(node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state') &&
102+
node.object.type === 'MemberExpression'
103+
);
104+
105+
if (isPropUsed && options.class === 'always') {
106+
context.report({
107+
node: node,
108+
message: `Must use destructuring ${node.object.property.name} assignment`
109+
});
110+
}
111+
}
112+
113+
return {
114+
115+
FunctionDeclaration: handleStatelessComponent,
116+
117+
ArrowFunctionExpression: handleStatelessComponent,
118+
119+
FunctionExpression: handleStatelessComponent,
120+
121+
MemberExpression: function(node) {
122+
const SFCComponent = components.get(context.getScope(node).block);
123+
const classComponent = utils.getParentComponent(node);
124+
if (SFCComponent) {
125+
handleSFCUsage(node);
126+
}
127+
if (classComponent) {
128+
handleClassUsage(node, classComponent);
129+
}
130+
}
131+
};
132+
})
133+
};

0 commit comments

Comments
 (0)