Skip to content

Commit 6fdefcf

Browse files
committed
Add jsx-sort-default-props rule
1 parent 07345b4 commit 6fdefcf

File tree

5 files changed

+743
-0
lines changed

5 files changed

+743
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Enable the rules that you would like to use.
156156
* [react/jsx-one-expression-per-line](docs/rules/jsx-one-expression-per-line.md): Limit to one expression per line in JSX
157157
* [react/jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md): Enforce curly braces or disallow unnecessary curly braces in JSX
158158
* [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
159+
* [react/jsx-sort-default-props](docs/rules/jsx-sort-default-props.md): Enforce default props alphabetical sorting
159160
* [react/jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting (fixable)
160161
* [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
161162
* [react/jsx-tag-spacing](docs/rules/jsx-tag-spacing.md): Validate whitespace in and around the JSX opening and closing brackets (fixable)

docs/rules/jsx-sort-default-props.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Enforce propTypes declarations alphabetical sorting (react/jsx-sort-default-props)
2+
3+
Some developers prefer to sort propTypes declarations alphabetically to be able to find necessary declaration easier at the later time. Others feel that it adds complexity and becomes burden to maintain.
4+
5+
## Rule Details
6+
7+
This rule checks all components and verifies that all defaultProps declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive.
8+
9+
The following patterns are considered warnings:
10+
11+
```jsx
12+
var Component = createReactClass({
13+
...
14+
getDefaultProps: function() {
15+
return {
16+
z: "z",
17+
a: "a",
18+
b: "b"
19+
};
20+
},
21+
...
22+
});
23+
24+
class Component extends React.Component {
25+
...
26+
}
27+
Component.defaultProps = {
28+
z: "z",
29+
a: "a",
30+
b: "b"
31+
};
32+
33+
class Component extends React.Component {
34+
static defaultProps = {
35+
z: "z",
36+
y: "y",
37+
a: "a"
38+
}
39+
render() {
40+
return <div />;
41+
}
42+
}
43+
```
44+
45+
The following patterns are considered okay and do not cause warnings:
46+
47+
```jsx
48+
var Component = createReactClass({
49+
...
50+
getDefaultProps: function() {
51+
return {
52+
a: "a",
53+
b: "b",
54+
c: "c"
55+
};
56+
},
57+
...
58+
});
59+
60+
class Component extends React.Component {
61+
...
62+
}
63+
Component.defaultProps = {
64+
a: "a",
65+
b: "b",
66+
c: "c"
67+
};
68+
69+
class Component extends React.Component {
70+
static defaultProps = {
71+
a: PropTypes.any,
72+
b: PropTypes.any,
73+
c: PropTypes.any
74+
}
75+
render() {
76+
return <div />;
77+
}
78+
}
79+
```
80+
81+
## Rule Options
82+
83+
```js
84+
...
85+
"react/jsx-sort-default-props": [<enabled>, {
86+
"ignoreCase": <boolean>,
87+
}]
88+
...
89+
```
90+
91+
### `ignoreCase`
92+
93+
When `true` the rule ignores the case-sensitivity of the declarations order.
94+
95+
## When not to use
96+
97+
This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing defaultProps declarations isn't a part of your coding standards, then you can leave this rule off.

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const allRules = {
3535
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
3636
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'),
3737
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
38+
'jsx-sort-default-props': require('./lib/rules/jsx-sort-default-props'),
3839
'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
3940
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
4041
'jsx-tag-spacing': require('./lib/rules/jsx-tag-spacing'),

lib/rules/jsx-sort-default-props.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* @fileoverview Enforce default props alphabetical sorting
3+
* @author Vladimir Kattsov
4+
*/
5+
'use strict';
6+
7+
// const variableUtil = require('../util/variable');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Enforce default props alphabetical sorting',
17+
category: 'Stylistic Issues',
18+
recommended: false
19+
},
20+
21+
schema: [{
22+
type: 'object',
23+
properties: {
24+
ignoreCase: {
25+
type: 'boolean'
26+
}
27+
},
28+
additionalProperties: false
29+
}]
30+
},
31+
32+
create: function(context) {
33+
const sourceCode = context.getSourceCode();
34+
const configuration = context.options[0] || {};
35+
const ignoreCase = configuration.ignoreCase || false;
36+
// const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
37+
38+
/**
39+
* Get properties name
40+
* @param {Object} node - Property.
41+
* @returns {String} Property name.
42+
*/
43+
function getPropertyName(node) {
44+
if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
45+
return node.key.name;
46+
} else if (node.type === 'MemberExpression') {
47+
return node.property.name;
48+
// Special case for class properties
49+
// (babel-eslint@5 does not expose property name so we have to rely on tokens)
50+
} else if (node.type === 'ClassProperty') {
51+
const tokens = context.getFirstTokens(node, 2);
52+
return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
53+
}
54+
return '';
55+
}
56+
57+
/**
58+
* Checks if the Identifier node passed in looks like a defaultProps declaration.
59+
* @param {ASTNode} node The node to check. Must be an Identifier node.
60+
* @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not
61+
*/
62+
function isDefaultPropsDeclaration(node) {
63+
const propName = getPropertyName(node);
64+
return (propName === 'defaultProps' || propName === 'getDefaultProps');
65+
}
66+
67+
function getKey(node) {
68+
return sourceCode.getText(node.key || node.argument);
69+
}
70+
71+
/**
72+
* Find a variable by name in the current scope.
73+
* @param {string} name Name of the variable to look for.
74+
* @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
75+
*/
76+
// function findVariableByName(name) {
77+
// const variable = variableUtil.variablesInScope(context).find(item => item.name === name);
78+
//
79+
// if (!variable || !variable.defs[0] || !variable.defs[0].node) {
80+
// return null;
81+
// }
82+
//
83+
// if (variable.defs[0].node.type === 'TypeAlias') {
84+
// return variable.defs[0].node.right;
85+
// }
86+
//
87+
// return variable.defs[0].node.init;
88+
// }
89+
90+
/**
91+
* Checks if defaultProps declarations are sorted
92+
* @param {Array} declarations The array of AST nodes being checked.
93+
* @returns {void}
94+
*/
95+
function checkSorted(declarations) {
96+
declarations.reduce((prev, curr, idx, decls) => {
97+
if (/SpreadProperty$/.test(curr.type)) {
98+
return decls[idx + 1];
99+
}
100+
101+
let prevPropName = getKey(prev);
102+
let currentPropName = getKey(curr);
103+
104+
if (ignoreCase) {
105+
prevPropName = prevPropName.toLowerCase();
106+
currentPropName = currentPropName.toLowerCase();
107+
}
108+
109+
if (currentPropName < prevPropName) {
110+
context.report({
111+
node: curr,
112+
message: 'Default prop types declarations should be sorted alphabetically'
113+
});
114+
115+
return prev;
116+
}
117+
118+
return curr;
119+
}, declarations[0]);
120+
}
121+
122+
function checkNode(node) {
123+
switch (node && node.type) {
124+
case 'ObjectExpression':
125+
checkSorted(node.properties);
126+
break;
127+
// case 'Identifier':
128+
// const propTypesObject = findVariableByName(node.name);
129+
// if (propTypesObject && propTypesObject.properties) {
130+
// checkSorted(propTypesObject.properties);
131+
// }
132+
// break;
133+
// case 'CallExpression':
134+
// const innerNode = node.arguments && node.arguments[0];
135+
// if (propWrapperFunctions.has(node.callee.name) && innerNode) {
136+
// checkNode(innerNode);
137+
// }
138+
// break;
139+
default:
140+
break;
141+
}
142+
}
143+
144+
// --------------------------------------------------------------------------
145+
// Public API
146+
// --------------------------------------------------------------------------
147+
148+
return {
149+
ClassProperty: function(node) {
150+
if (!isDefaultPropsDeclaration(node)) {
151+
return;
152+
}
153+
154+
checkNode(node.value);
155+
},
156+
157+
MemberExpression: function(node) {
158+
if (!isDefaultPropsDeclaration(node)) {
159+
return;
160+
}
161+
162+
checkNode(node.parent.right);
163+
}
164+
};
165+
}
166+
};

0 commit comments

Comments
 (0)