Skip to content

Commit 24b1a98

Browse files
Finn Paulsljharb
Finn Pauls
authored andcommitted
Add fixer for sort-prop-types
1 parent 6b179ed commit 24b1a98

File tree

3 files changed

+574
-29
lines changed

3 files changed

+574
-29
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

+90-3
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
}

0 commit comments

Comments
 (0)