Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fixer for sort-prop-types #1891

Merged
merged 2 commits into from Jul 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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