Skip to content

Commit a0100f3

Browse files
authored
Merge pull request jsx-eslint#1891 from finnp/sort-prop-types-fixer
Add fixer for sort-prop-types
2 parents 32b751f + 24b1a98 commit a0100f3

File tree

3 files changed

+575
-30
lines changed

3 files changed

+575
-30
lines changed

docs/rules/sort-prop-types.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
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.
44

5+
**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
6+
7+
58
## Rule Details
69

710
This rule checks all components and verifies that all propTypes declarations are sorted alphabetically. A spread attribute resets the verification. The default configuration of the rule is case-sensitive.

lib/rules/sort-prop-types.js

+91-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module.exports = {
2020
url: docsUrl('sort-prop-types')
2121
},
2222

23+
fixable: 'code',
24+
2325
schema: [{
2426
type: 'object',
2527
properties: {
@@ -71,6 +73,46 @@ module.exports = {
7173
);
7274
}
7375

76+
function getShapeProperties (node) {
77+
return node.arguments && node.arguments[0] && node.arguments[0].properties;
78+
}
79+
80+
function sorter(a, b) {
81+
let aKey = getKey(a);
82+
let bKey = getKey(b);
83+
if (requiredFirst) {
84+
if (isRequiredProp(a) && !isRequiredProp(b)) {
85+
return -1;
86+
}
87+
if (!isRequiredProp(a) && isRequiredProp(b)) {
88+
return 1;
89+
}
90+
}
91+
92+
if (callbacksLast) {
93+
if (isCallbackPropName(aKey) && !isCallbackPropName(bKey)) {
94+
return 1;
95+
}
96+
if (!isCallbackPropName(aKey) && isCallbackPropName(bKey)) {
97+
return -1;
98+
}
99+
}
100+
101+
if (ignoreCase) {
102+
aKey = aKey.toLowerCase();
103+
bKey = bKey.toLowerCase();
104+
}
105+
106+
if (aKey < bKey) {
107+
return -1;
108+
}
109+
if (aKey > bKey) {
110+
return 1;
111+
}
112+
return 0;
113+
}
114+
115+
74116
/**
75117
* Checks if propTypes declarations are sorted
76118
* @param {Array} declarations The array of AST nodes being checked.
@@ -83,6 +125,48 @@ module.exports = {
83125
return;
84126
}
85127

128+
function fix(fixer) {
129+
function sortInSource(allNodes, source) {
130+
const originalSource = source;
131+
const nodeGroups = allNodes.reduce((acc, curr) => {
132+
if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
133+
acc.push([]);
134+
} else {
135+
acc[acc.length - 1].push(curr);
136+
}
137+
return acc;
138+
}, [[]]);
139+
140+
nodeGroups.forEach(nodes => {
141+
const sortedAttributes = nodes.slice().sort(sorter);
142+
143+
for (let i = nodes.length - 1; i >= 0; i--) {
144+
const sortedAttr = sortedAttributes[i];
145+
const attr = nodes[i];
146+
let sortedAttrText = sourceCode.getText(sortedAttr);
147+
if (sortShapeProp && isShapeProp(sortedAttr.value)) {
148+
const shape = getShapeProperties(sortedAttr.value);
149+
if (shape) {
150+
const attrSource = sortInSource(
151+
shape,
152+
originalSource
153+
);
154+
sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
155+
}
156+
}
157+
source = `${source.slice(0, attr.range[0])}${sortedAttrText}${source.slice(attr.range[1])}`;
158+
}
159+
});
160+
return source;
161+
}
162+
163+
const source = sortInSource(declarations, context.getSourceCode().getText());
164+
165+
const rangeStart = declarations[0].range[0];
166+
const rangeEnd = declarations[declarations.length - 1].range[1];
167+
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
168+
}
169+
86170
declarations.reduce((prev, curr, idx, decls) => {
87171
if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
88172
return decls[idx + 1];
@@ -109,7 +193,8 @@ module.exports = {
109193
// Encountered a non-required prop after a required prop
110194
context.report({
111195
node: curr,
112-
message: 'Required prop types must be listed before all other prop types'
196+
message: 'Required prop types must be listed before all other prop types',
197+
fix
113198
});
114199
return curr;
115200
}
@@ -124,7 +209,8 @@ module.exports = {
124209
// Encountered a non-callback prop after a callback prop
125210
context.report({
126211
node: prev,
127-
message: 'Callback prop types must be listed after all other prop types'
212+
message: 'Callback prop types must be listed after all other prop types',
213+
fix
128214
});
129215
return prev;
130216
}
@@ -133,7 +219,8 @@ module.exports = {
133219
if (currentPropName < prevPropName) {
134220
context.report({
135221
node: curr,
136-
message: 'Prop types declarations should be sorted alphabetically'
222+
message: 'Prop types declarations should be sorted alphabetically',
223+
fix
137224
});
138225
return prev;
139226
}
@@ -166,7 +253,7 @@ module.exports = {
166253

167254
return {
168255
CallExpression: function(node) {
169-
if (!sortShapeProp || !isShapeProp(node) || (!node.arguments && !node.arguments[0])) {
256+
if (!sortShapeProp || !isShapeProp(node) || !(node.arguments && node.arguments[0])) {
170257
return;
171258
}
172259
checkSorted(node.arguments[0].properties);

0 commit comments

Comments
 (0)