Skip to content

Commit

Permalink
Add fixer for sort-prop-types
Browse files Browse the repository at this point in the history
  • Loading branch information
Finn Pauls authored and ljharb committed Jul 20, 2018
1 parent 6b179ed commit 24b1a98
Show file tree
Hide file tree
Showing 3 changed files with 574 additions and 29 deletions.
3 changes: 3 additions & 0 deletions docs/rules/sort-prop-types.md
Expand Up @@ -2,6 +2,9 @@

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.

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.


## Rule Details

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.
Expand Down
93 changes: 90 additions & 3 deletions lib/rules/sort-prop-types.js
Expand Up @@ -20,6 +20,8 @@ module.exports = {
url: docsUrl('sort-prop-types')
},

fixable: 'code',

schema: [{
type: 'object',
properties: {
Expand Down Expand Up @@ -71,6 +73,46 @@ module.exports = {
);
}

function getShapeProperties (node) {
return node.arguments && node.arguments[0] && node.arguments[0].properties;
}

function sorter(a, b) {
let aKey = getKey(a);
let bKey = getKey(b);
if (requiredFirst) {
if (isRequiredProp(a) && !isRequiredProp(b)) {
return -1;
}
if (!isRequiredProp(a) && isRequiredProp(b)) {
return 1;
}
}

if (callbacksLast) {
if (isCallbackPropName(aKey) && !isCallbackPropName(bKey)) {
return 1;
}
if (!isCallbackPropName(aKey) && isCallbackPropName(bKey)) {
return -1;
}
}

if (ignoreCase) {
aKey = aKey.toLowerCase();
bKey = bKey.toLowerCase();
}

if (aKey < bKey) {
return -1;
}
if (aKey > bKey) {
return 1;
}
return 0;
}


/**
* Checks if propTypes declarations are sorted
* @param {Array} declarations The array of AST nodes being checked.
Expand All @@ -83,6 +125,48 @@ module.exports = {
return;
}

function fix(fixer) {
function sortInSource(allNodes, source) {
const originalSource = source;
const nodeGroups = allNodes.reduce((acc, curr) => {
if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
acc.push([]);
} else {
acc[acc.length - 1].push(curr);
}
return acc;
}, [[]]);

nodeGroups.forEach(nodes => {
const sortedAttributes = nodes.slice().sort(sorter);

for (let i = nodes.length - 1; i >= 0; i--) {
const sortedAttr = sortedAttributes[i];
const attr = nodes[i];
let sortedAttrText = sourceCode.getText(sortedAttr);
if (sortShapeProp && isShapeProp(sortedAttr.value)) {
const shape = getShapeProperties(sortedAttr.value);
if (shape) {
const attrSource = sortInSource(
shape,
originalSource
);
sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
}
}
source = `${source.slice(0, attr.range[0])}${sortedAttrText}${source.slice(attr.range[1])}`;
}
});
return source;
}

const source = sortInSource(declarations, context.getSourceCode().getText());

const rangeStart = declarations[0].range[0];
const rangeEnd = declarations[declarations.length - 1].range[1];
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
}

declarations.reduce((prev, curr, idx, decls) => {
if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
return decls[idx + 1];
Expand All @@ -109,7 +193,8 @@ module.exports = {
// Encountered a non-required prop after a required prop
context.report({
node: curr,
message: 'Required prop types must be listed before all other prop types'
message: 'Required prop types must be listed before all other prop types',
fix
});
return curr;
}
Expand All @@ -124,7 +209,8 @@ module.exports = {
// Encountered a non-callback prop after a callback prop
context.report({
node: prev,
message: 'Callback prop types must be listed after all other prop types'
message: 'Callback prop types must be listed after all other prop types',
fix
});
return prev;
}
Expand All @@ -133,7 +219,8 @@ module.exports = {
if (currentPropName < prevPropName) {
context.report({
node: curr,
message: 'Prop types declarations should be sorted alphabetically'
message: 'Prop types declarations should be sorted alphabetically',
fix
});
return prev;
}
Expand Down

0 comments on commit 24b1a98

Please sign in to comment.