Skip to content

Commit

Permalink
[New] sort-prop-types: support comments on prop types
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzherdev committed Sep 5, 2018
1 parent b161d7a commit e23cc32
Show file tree
Hide file tree
Showing 4 changed files with 1,510 additions and 23 deletions.
23 changes: 3 additions & 20 deletions lib/rules/jsx-curly-spacing.js
Expand Up @@ -11,6 +11,7 @@
'use strict';

const has = require('has');
const commentsUtil = require('../util/comments');
const docsUrl = require('../util/docsUrl');

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -228,16 +229,7 @@ module.exports = {
message: `There should be no space after '${token.value}'`,
fix: function(fixer) {
const nextToken = sourceCode.getTokenAfter(token);
let nextComment;

// ESLint >=4.x
if (sourceCode.getCommentsAfter) {
nextComment = sourceCode.getCommentsAfter(token);
// ESLint 3.x
} else {
const potentialComment = sourceCode.getTokenAfter(token, {includeComments: true});
nextComment = nextToken === potentialComment ? [] : [potentialComment];
}
const nextComment = commentsUtil.getCommentsAfter(token, sourceCode);

// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
if (nextComment.length > 0) {
Expand All @@ -262,16 +254,7 @@ module.exports = {
message: `There should be no space before '${token.value}'`,
fix: function(fixer) {
const previousToken = sourceCode.getTokenBefore(token);
let previousComment;

// ESLint >=4.x
if (sourceCode.getCommentsBefore) {
previousComment = sourceCode.getCommentsBefore(token);
// ESLint 3.x
} else {
const potentialComment = sourceCode.getTokenBefore(token, {includeComments: true});
previousComment = previousToken === potentialComment ? [] : [potentialComment];
}
const previousComment = commentsUtil.getCommentsBefore(token, sourceCode);

// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
if (previousComment.length > 0) {
Expand Down
109 changes: 106 additions & 3 deletions lib/rules/sort-prop-types.js
Expand Up @@ -5,6 +5,7 @@

const variableUtil = require('../util/variable');
const propsUtil = require('../util/props');
const commentsUtil = require('../util/comments');
const docsUrl = require('../util/docsUrl');

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -55,6 +56,7 @@ module.exports = {
const noSortAlphabetically = configuration.noSortAlphabetically || false;
const sortShapeProp = configuration.sortShapeProp || false;
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
const commentsAttachment = context.settings.comments || 'above';

function getKey(node) {
return sourceCode.getText(node.key || node.argument);
Expand Down Expand Up @@ -117,6 +119,90 @@ module.exports = {
return 0;
}

function getRelatedComments(node, nextNode) {
// check for an end of line comment
const nextNodeComments = nextNode
? commentsUtil.getCommentsBefore(nextNode, sourceCode)
: commentsUtil.getCommentsAfter(node, sourceCode);
if (nextNodeComments.length === 1) {
const comment = nextNodeComments[0];
if (comment.loc.start.line === comment.loc.end.line && comment.loc.end.line === node.loc.end.line) {
return [nextNodeComments, true];
}
}

if (commentsAttachment === 'above') {
return [commentsUtil.getCommentsBefore(node, sourceCode), false];
}

return [nextNodeComments, false];
}

function replaceNodeWithText(source, originalNode, sortedNodeText) {
return `${source.slice(0, originalNode.range[0])}${sortedNodeText}${source.slice(originalNode.range[1])}`;
}

function sortNodeWithComments(source, originalAttr, originalComments, sortedAttrText, sortedComments) {
if (sortedComments.length && originalComments.length) {
const swapComments = () => {
const sortedCommentsText = sourceCode.getText().slice(
sortedComments[0].range[0],
sortedComments[sortedComments.length - 1].range[1]
);
return `${source.slice(0, originalComments[0].range[0])}${sortedCommentsText}${source.slice(originalComments[originalComments.length - 1].range[1])}`;
};
if (originalAttr.range[1] < originalComments[0].range[0]) {
source = swapComments();
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
} else {
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
source = swapComments();
}
return source;
}

if (sortedComments.length) {
const sortedCommentsText = sourceCode.getText().slice(
sortedComments[0].range[0],
sortedComments[sortedComments.length - 1].range[1]
);

const indent = Array(sortedComments[0].loc.start.column + 1).join(' ');
if (commentsAttachment === 'above') {
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
source = `${source.slice(0, originalAttr.range[0])}${sortedCommentsText}\n${indent}${source.slice(originalAttr.range[0])}`;
} else {
const nextToken = sourceCode.getTokenAfter(originalAttr);
const targetIndex = nextToken.value === ',' ? nextToken.range[1] : originalAttr.range[1];
source = `${source.slice(0, targetIndex)}\n${indent}${sortedCommentsText}${source.slice(targetIndex)}`;
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
}
return source;
}

if (originalComments.length) {
const removeComments = () => {
const startLoc = sourceCode.getLocFromIndex(originalComments[0].range[0]);
const lineStart = sourceCode.getIndexFromLoc({line: startLoc.line, column: 0});
const endLoc = sourceCode.getLocFromIndex(originalComments[originalComments.length - 1].range[1]);
const lineEnd = sourceCode.getIndexFromLoc({
line: endLoc.line,
column: sourceCode.lines[endLoc.line - 1].length - 1
});
return `${source.slice(0, lineStart)}${source.slice(lineEnd + 2)}`;
};
if (originalAttr.range[1] < originalComments[0].range[0]) {
source = removeComments();
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
} else {
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
source = removeComments();
}
return source;
}

return null;
}

/**
* Checks if propTypes declarations are sorted
Expand Down Expand Up @@ -148,7 +234,16 @@ module.exports = {
for (let i = nodes.length - 1; i >= 0; i--) {
const sortedAttr = sortedAttributes[i];
const attr = nodes[i];
if (sortedAttr === attr) {
continue;
}

const [sortedComments] = getRelatedComments(sortedAttr,
allNodes[allNodes.indexOf(sortedAttr) + 1]);
const [attrComments] = getRelatedComments(attr, nodes[i + 1]);

let sortedAttrText = sourceCode.getText(sortedAttr);

if (sortShapeProp && isShapeProp(sortedAttr.value)) {
const shape = getShapeProperties(sortedAttr.value);
if (shape) {
Expand All @@ -159,16 +254,24 @@ module.exports = {
sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
}
}
source = `${source.slice(0, attr.range[0])}${sortedAttrText}${source.slice(attr.range[1])}`;

const newSource = sortNodeWithComments(source, attr, attrComments, sortedAttrText, sortedComments);
source = newSource || replaceNodeWithText(source, attr, sortedAttrText);
}
});
return source;
}

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

const rangeStart = declarations[0].range[0];
const rangeEnd = declarations[declarations.length - 1].range[1];
const [startComments, isSameLineStart] = getRelatedComments(declarations[0], declarations[1]);
const [endComments, isSameLineEnd] = getRelatedComments(declarations[declarations.length - 1], null);
const rangeStart = (commentsAttachment === 'above' && startComments.length && !isSameLineStart)
? startComments[0].range[0]
: declarations[0].range[0];
const rangeEnd = (commentsAttachment === 'below' && endComments.length || isSameLineEnd)
? endComments[endComments.length - 1].range[1]
: declarations[declarations.length - 1].range[1];
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
}

Expand Down
76 changes: 76 additions & 0 deletions lib/util/comments.js
@@ -0,0 +1,76 @@
/**
* @fileoverview Utility functions for comments handling.
*/
'use strict';

/**
* Backports sourceCode.getCommentsBefore() for ESLint 3
*
* @param {Object} nodeOrToken Node or token to get comments for.
* @param {Object} sourceCode The SourceCode object.
* @returns {Array} Array of comment tokens.
*/
function getCommentsBefore(nodeOrToken, sourceCode) {
const token = sourceCode.getFirstToken(nodeOrToken, {includeComments: true});
let previousComments = [];

// ESLint >=4.x
if (sourceCode.getCommentsBefore) {
previousComments = sourceCode.getCommentsBefore(token);
// ESLint 3.x
} else {
let currentToken = token;
do {
const previousToken = sourceCode.getTokenBefore(currentToken);
const potentialComment = sourceCode.getTokenBefore(currentToken, {includeComments: true});

if (previousToken !== potentialComment) {
previousComments.push(potentialComment);
currentToken = potentialComment;
} else {
currentToken = null;
}
} while (currentToken);
previousComments = previousComments.reverse();
}

return previousComments;
}

/**
* Backports sourceCode.getCommentsAfter() for ESLint 3
*
* @param {Object} nodeOrToken Node or token to get comments for.
* @param {Object} sourceCode The SourceCode object.
* @returns {Array} Array of comment tokens.
*/
function getCommentsAfter(nodeOrToken, sourceCode) {
const token = sourceCode.getLastToken(nodeOrToken, {includeComments: true});
let nextComments = [];

// ESLint >=4.x
if (sourceCode.getCommentsAfter) {
nextComments = sourceCode.getCommentsAfter(token);
// ESLint 3.x
} else {
let currentToken = token;
do {
const nextToken = sourceCode.getTokenAfter(currentToken);
const potentialComment = sourceCode.getTokenAfter(currentToken, {includeComments: true});

if (nextToken !== potentialComment) {
nextComments.push(potentialComment);
currentToken = potentialComment;
} else {
currentToken = null;
}
} while (currentToken);
}

return nextComments;
}

module.exports = {
getCommentsBefore: getCommentsBefore,
getCommentsAfter: getCommentsAfter
};

0 comments on commit e23cc32

Please sign in to comment.