Skip to content

Commit 67b88a8

Browse files
committed
[New] sort-prop-types: support comments on prop types
1 parent d2b5b73 commit 67b88a8

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
const propWrapperUtil = require('../util/propWrapper');
1011

@@ -55,6 +56,7 @@ module.exports = {
5556
const ignoreCase = configuration.ignoreCase || false;
5657
const noSortAlphabetically = configuration.noSortAlphabetically || false;
5758
const sortShapeProp = configuration.sortShapeProp || false;
59+
const commentsAttachment = context.settings.comments || 'above';
5860

5961
function getKey(node) {
6062
if (node.key && node.key.value) {
@@ -120,6 +122,90 @@ module.exports = {
120122
return 0;
121123
}
122124

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

124210
/**
125211
* Checks if propTypes declarations are sorted
@@ -151,7 +237,16 @@ module.exports = {
151237
for (let i = nodes.length - 1; i >= 0; i--) {
152238
const sortedAttr = sortedAttributes[i];
153239
const attr = nodes[i];
240+
if (sortedAttr === attr) {
241+
continue;
242+
}
243+
244+
const sortedComments = getRelatedComments(sortedAttr,
245+
allNodes[allNodes.indexOf(sortedAttr) + 1]).comments;
246+
const attrComments = getRelatedComments(attr, nodes[i + 1]).comments;
247+
154248
let sortedAttrText = sourceCode.getText(sortedAttr);
249+
155250
if (sortShapeProp && isShapeProp(sortedAttr.value)) {
156251
const shape = getShapeProperties(sortedAttr.value);
157252
if (shape) {
@@ -162,16 +257,24 @@ module.exports = {
162257
sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
163258
}
164259
}
165-
source = `${source.slice(0, attr.range[0])}${sortedAttrText}${source.slice(attr.range[1])}`;
260+
261+
const newSource = sortNodeWithComments(source, attr, attrComments, sortedAttrText, sortedComments);
262+
source = newSource || replaceNodeWithText(source, attr, sortedAttrText);
166263
}
167264
});
168265
return source;
169266
}
170267

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

173-
const rangeStart = declarations[0].range[0];
174-
const rangeEnd = declarations[declarations.length - 1].range[1];
270+
const startComments = getRelatedComments(declarations[0], declarations[1]);
271+
const endComments = getRelatedComments(declarations[declarations.length - 1], null);
272+
const rangeStart = (commentsAttachment === 'above' && startComments.comments.length && !startComments.isSameLine)
273+
? startComments.comments[0].range[0]
274+
: declarations[0].range[0];
275+
const rangeEnd = (commentsAttachment === 'below' && endComments.comments.length || endComments.isSameLine)
276+
? endComments.comments[endComments.comments.length - 1].range[1]
277+
: declarations[declarations.length - 1].range[1];
175278
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
176279
}
177280

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)