Skip to content

Commit e23cc32

Browse files
committed
[New] sort-prop-types: support comments on prop types
1 parent b161d7a commit e23cc32

File tree

4 files changed

+1510
-23
lines changed

4 files changed

+1510
-23
lines changed

lib/rules/jsx-curly-spacing.js

+3-20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
'use strict';
1212

1313
const has = require('has');
14+
const commentsUtil = require('../util/comments');
1415
const docsUrl = require('../util/docsUrl');
1516

1617
// ------------------------------------------------------------------------------
@@ -228,16 +229,7 @@ module.exports = {
228229
message: `There should be no space after '${token.value}'`,
229230
fix: function(fixer) {
230231
const nextToken = sourceCode.getTokenAfter(token);
231-
let nextComment;
232-
233-
// ESLint >=4.x
234-
if (sourceCode.getCommentsAfter) {
235-
nextComment = sourceCode.getCommentsAfter(token);
236-
// ESLint 3.x
237-
} else {
238-
const potentialComment = sourceCode.getTokenAfter(token, {includeComments: true});
239-
nextComment = nextToken === potentialComment ? [] : [potentialComment];
240-
}
232+
const nextComment = commentsUtil.getCommentsAfter(token, sourceCode);
241233

242234
// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
243235
if (nextComment.length > 0) {
@@ -262,16 +254,7 @@ module.exports = {
262254
message: `There should be no space before '${token.value}'`,
263255
fix: function(fixer) {
264256
const previousToken = sourceCode.getTokenBefore(token);
265-
let previousComment;
266-
267-
// ESLint >=4.x
268-
if (sourceCode.getCommentsBefore) {
269-
previousComment = sourceCode.getCommentsBefore(token);
270-
// ESLint 3.x
271-
} else {
272-
const potentialComment = sourceCode.getTokenBefore(token, {includeComments: true});
273-
previousComment = previousToken === potentialComment ? [] : [potentialComment];
274-
}
257+
const previousComment = commentsUtil.getCommentsBefore(token, sourceCode);
275258

276259
// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
277260
if (previousComment.length > 0) {

lib/rules/sort-prop-types.js

+106-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
const variableUtil = require('../util/variable');
77
const propsUtil = require('../util/props');
8+
const commentsUtil = require('../util/comments');
89
const docsUrl = require('../util/docsUrl');
910

1011
// ------------------------------------------------------------------------------
@@ -55,6 +56,7 @@ module.exports = {
5556
const noSortAlphabetically = configuration.noSortAlphabetically || false;
5657
const sortShapeProp = configuration.sortShapeProp || false;
5758
const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []);
59+
const commentsAttachment = context.settings.comments || 'above';
5860

5961
function getKey(node) {
6062
return sourceCode.getText(node.key || node.argument);
@@ -117,6 +119,90 @@ module.exports = {
117119
return 0;
118120
}
119121

122+
function getRelatedComments(node, nextNode) {
123+
// check for an end of line comment
124+
const nextNodeComments = nextNode
125+
? commentsUtil.getCommentsBefore(nextNode, sourceCode)
126+
: commentsUtil.getCommentsAfter(node, sourceCode);
127+
if (nextNodeComments.length === 1) {
128+
const comment = nextNodeComments[0];
129+
if (comment.loc.start.line === comment.loc.end.line && comment.loc.end.line === node.loc.end.line) {
130+
return [nextNodeComments, true];
131+
}
132+
}
133+
134+
if (commentsAttachment === 'above') {
135+
return [commentsUtil.getCommentsBefore(node, sourceCode), false];
136+
}
137+
138+
return [nextNodeComments, false];
139+
}
140+
141+
function replaceNodeWithText(source, originalNode, sortedNodeText) {
142+
return `${source.slice(0, originalNode.range[0])}${sortedNodeText}${source.slice(originalNode.range[1])}`;
143+
}
144+
145+
function sortNodeWithComments(source, originalAttr, originalComments, sortedAttrText, sortedComments) {
146+
if (sortedComments.length && originalComments.length) {
147+
const swapComments = () => {
148+
const sortedCommentsText = sourceCode.getText().slice(
149+
sortedComments[0].range[0],
150+
sortedComments[sortedComments.length - 1].range[1]
151+
);
152+
return `${source.slice(0, originalComments[0].range[0])}${sortedCommentsText}${source.slice(originalComments[originalComments.length - 1].range[1])}`;
153+
};
154+
if (originalAttr.range[1] < originalComments[0].range[0]) {
155+
source = swapComments();
156+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
157+
} else {
158+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
159+
source = swapComments();
160+
}
161+
return source;
162+
}
163+
164+
if (sortedComments.length) {
165+
const sortedCommentsText = sourceCode.getText().slice(
166+
sortedComments[0].range[0],
167+
sortedComments[sortedComments.length - 1].range[1]
168+
);
169+
170+
const indent = Array(sortedComments[0].loc.start.column + 1).join(' ');
171+
if (commentsAttachment === 'above') {
172+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
173+
source = `${source.slice(0, originalAttr.range[0])}${sortedCommentsText}\n${indent}${source.slice(originalAttr.range[0])}`;
174+
} else {
175+
const nextToken = sourceCode.getTokenAfter(originalAttr);
176+
const targetIndex = nextToken.value === ',' ? nextToken.range[1] : originalAttr.range[1];
177+
source = `${source.slice(0, targetIndex)}\n${indent}${sortedCommentsText}${source.slice(targetIndex)}`;
178+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
179+
}
180+
return source;
181+
}
182+
183+
if (originalComments.length) {
184+
const removeComments = () => {
185+
const startLoc = sourceCode.getLocFromIndex(originalComments[0].range[0]);
186+
const lineStart = sourceCode.getIndexFromLoc({line: startLoc.line, column: 0});
187+
const endLoc = sourceCode.getLocFromIndex(originalComments[originalComments.length - 1].range[1]);
188+
const lineEnd = sourceCode.getIndexFromLoc({
189+
line: endLoc.line,
190+
column: sourceCode.lines[endLoc.line - 1].length - 1
191+
});
192+
return `${source.slice(0, lineStart)}${source.slice(lineEnd + 2)}`;
193+
};
194+
if (originalAttr.range[1] < originalComments[0].range[0]) {
195+
source = removeComments();
196+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
197+
} else {
198+
source = replaceNodeWithText(source, originalAttr, sortedAttrText);
199+
source = removeComments();
200+
}
201+
return source;
202+
}
203+
204+
return null;
205+
}
120206

121207
/**
122208
* Checks if propTypes declarations are sorted
@@ -148,7 +234,16 @@ module.exports = {
148234
for (let i = nodes.length - 1; i >= 0; i--) {
149235
const sortedAttr = sortedAttributes[i];
150236
const attr = nodes[i];
237+
if (sortedAttr === attr) {
238+
continue;
239+
}
240+
241+
const [sortedComments] = getRelatedComments(sortedAttr,
242+
allNodes[allNodes.indexOf(sortedAttr) + 1]);
243+
const [attrComments] = getRelatedComments(attr, nodes[i + 1]);
244+
151245
let sortedAttrText = sourceCode.getText(sortedAttr);
246+
152247
if (sortShapeProp && isShapeProp(sortedAttr.value)) {
153248
const shape = getShapeProperties(sortedAttr.value);
154249
if (shape) {
@@ -159,16 +254,24 @@ module.exports = {
159254
sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
160255
}
161256
}
162-
source = `${source.slice(0, attr.range[0])}${sortedAttrText}${source.slice(attr.range[1])}`;
257+
258+
const newSource = sortNodeWithComments(source, attr, attrComments, sortedAttrText, sortedComments);
259+
source = newSource || replaceNodeWithText(source, attr, sortedAttrText);
163260
}
164261
});
165262
return source;
166263
}
167264

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

170-
const rangeStart = declarations[0].range[0];
171-
const rangeEnd = declarations[declarations.length - 1].range[1];
267+
const [startComments, isSameLineStart] = getRelatedComments(declarations[0], declarations[1]);
268+
const [endComments, isSameLineEnd] = getRelatedComments(declarations[declarations.length - 1], null);
269+
const rangeStart = (commentsAttachment === 'above' && startComments.length && !isSameLineStart)
270+
? startComments[0].range[0]
271+
: declarations[0].range[0];
272+
const rangeEnd = (commentsAttachment === 'below' && endComments.length || isSameLineEnd)
273+
? endComments[endComments.length - 1].range[1]
274+
: declarations[declarations.length - 1].range[1];
172275
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
173276
}
174277

lib/util/comments.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @fileoverview Utility functions for comments handling.
3+
*/
4+
'use strict';
5+
6+
/**
7+
* Backports sourceCode.getCommentsBefore() for ESLint 3
8+
*
9+
* @param {Object} nodeOrToken Node or token to get comments for.
10+
* @param {Object} sourceCode The SourceCode object.
11+
* @returns {Array} Array of comment tokens.
12+
*/
13+
function getCommentsBefore(nodeOrToken, sourceCode) {
14+
const token = sourceCode.getFirstToken(nodeOrToken, {includeComments: true});
15+
let previousComments = [];
16+
17+
// ESLint >=4.x
18+
if (sourceCode.getCommentsBefore) {
19+
previousComments = sourceCode.getCommentsBefore(token);
20+
// ESLint 3.x
21+
} else {
22+
let currentToken = token;
23+
do {
24+
const previousToken = sourceCode.getTokenBefore(currentToken);
25+
const potentialComment = sourceCode.getTokenBefore(currentToken, {includeComments: true});
26+
27+
if (previousToken !== potentialComment) {
28+
previousComments.push(potentialComment);
29+
currentToken = potentialComment;
30+
} else {
31+
currentToken = null;
32+
}
33+
} while (currentToken);
34+
previousComments = previousComments.reverse();
35+
}
36+
37+
return previousComments;
38+
}
39+
40+
/**
41+
* Backports sourceCode.getCommentsAfter() for ESLint 3
42+
*
43+
* @param {Object} nodeOrToken Node or token to get comments for.
44+
* @param {Object} sourceCode The SourceCode object.
45+
* @returns {Array} Array of comment tokens.
46+
*/
47+
function getCommentsAfter(nodeOrToken, sourceCode) {
48+
const token = sourceCode.getLastToken(nodeOrToken, {includeComments: true});
49+
let nextComments = [];
50+
51+
// ESLint >=4.x
52+
if (sourceCode.getCommentsAfter) {
53+
nextComments = sourceCode.getCommentsAfter(token);
54+
// ESLint 3.x
55+
} else {
56+
let currentToken = token;
57+
do {
58+
const nextToken = sourceCode.getTokenAfter(currentToken);
59+
const potentialComment = sourceCode.getTokenAfter(currentToken, {includeComments: true});
60+
61+
if (nextToken !== potentialComment) {
62+
nextComments.push(potentialComment);
63+
currentToken = potentialComment;
64+
} else {
65+
currentToken = null;
66+
}
67+
} while (currentToken);
68+
}
69+
70+
return nextComments;
71+
}
72+
73+
module.exports = {
74+
getCommentsBefore: getCommentsBefore,
75+
getCommentsAfter: getCommentsAfter
76+
};

0 commit comments

Comments
 (0)