Skip to content

Commit

Permalink
Merge pull request #1891 from finnp/sort-prop-types-fixer
Browse files Browse the repository at this point in the history
Add fixer for sort-prop-types
  • Loading branch information
ljharb committed Jul 21, 2018
2 parents 32b751f + 24b1a98 commit a0100f3
Show file tree
Hide file tree
Showing 3 changed files with 575 additions and 30 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
95 changes: 91 additions & 4 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 Expand Up @@ -166,7 +253,7 @@ module.exports = {

return {
CallExpression: function(node) {
if (!sortShapeProp || !isShapeProp(node) || (!node.arguments && !node.arguments[0])) {
if (!sortShapeProp || !isShapeProp(node) || !(node.arguments && node.arguments[0])) {
return;
}
checkSorted(node.arguments[0].properties);
Expand Down

0 comments on commit a0100f3

Please sign in to comment.