Skip to content

Commit d5d2e82

Browse files
authored
Merge pull request #2250 from guliashvili/master
[new] Make all jsx-prop-sort lints fixable
2 parents f0c0b4d + 4b2d7f2 commit d5d2e82

File tree

2 files changed

+83
-30
lines changed

2 files changed

+83
-30
lines changed

lib/rules/jsx-sort-props.js

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,48 @@ function isReservedPropName(name, list) {
2727
return list.indexOf(name) >= 0;
2828
}
2929

30-
function propNameCompare(a, b, options) {
31-
if (options.ignoreCase) {
32-
a = a.toLowerCase();
33-
b = b.toLowerCase();
34-
}
30+
function contextCompare(a, b, options) {
31+
let aProp = propName(a);
32+
let bProp = propName(b);
33+
3534
if (options.reservedFirst) {
36-
const aIsReserved = isReservedPropName(a, options.reservedList);
37-
const bIsReserved = isReservedPropName(b, options.reservedList);
38-
if ((aIsReserved && bIsReserved) || (!aIsReserved && !bIsReserved)) {
39-
return a.localeCompare(b);
40-
} else if (aIsReserved && !bIsReserved) {
35+
const aIsReserved = isReservedPropName(aProp, options.reservedList);
36+
const bIsReserved = isReservedPropName(bProp, options.reservedList);
37+
if (aIsReserved && !bIsReserved) {
4138
return -1;
39+
} else if (!aIsReserved && bIsReserved) {
40+
return 1;
41+
}
42+
}
43+
44+
if (options.callbacksLast) {
45+
const aIsCallback = isCallbackPropName(aProp);
46+
const bIsCallback = isCallbackPropName(bProp);
47+
if (aIsCallback && !bIsCallback) {
48+
return 1;
49+
} else if (!aIsCallback && bIsCallback) {
50+
return -1;
51+
}
52+
}
53+
54+
if (options.shorthandFirst || options.shorthandLast) {
55+
const shorthandSign = options.shorthandFirst ? -1 : 1;
56+
if (!a.value && b.value) {
57+
return shorthandSign;
58+
} else if (a.value && !b.value) {
59+
return -shorthandSign;
4260
}
43-
return 1;
4461
}
45-
return a.localeCompare(b);
62+
63+
if (options.noSortAlphabetically) {
64+
return 0;
65+
}
66+
67+
if (options.ignoreCase) {
68+
aProp = aProp.toLowerCase();
69+
bProp = bProp.toLowerCase();
70+
}
71+
return aProp.localeCompare(bProp);
4672
}
4773

4874
/**
@@ -79,15 +105,21 @@ const generateFixerFunction = (node, context, reservedList) => {
79105
const attributes = node.attributes.slice(0);
80106
const configuration = context.options[0] || {};
81107
const ignoreCase = configuration.ignoreCase || false;
108+
const callbacksLast = configuration.callbacksLast || false;
109+
const shorthandFirst = configuration.shorthandFirst || false;
110+
const shorthandLast = configuration.shorthandLast || false;
111+
const noSortAlphabetically = configuration.noSortAlphabetically || false;
82112
const reservedFirst = configuration.reservedFirst || false;
83113

84114
// Sort props according to the context. Only supports ignoreCase.
85115
// Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
86116
// we only consider groups of sortable attributes.
117+
const options = {ignoreCase, callbacksLast, shorthandFirst, shorthandLast,
118+
noSortAlphabetically, reservedFirst, reservedList};
87119
const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes);
88120
const sortedAttributeGroups = sortableAttributeGroups.slice(0).map(group =>
89121
group.slice(0).sort((a, b) =>
90-
propNameCompare(propName(a), propName(b), {ignoreCase, reservedFirst, reservedList})
122+
contextCompare(a, b, options)
91123
)
92124
);
93125

@@ -267,7 +299,8 @@ module.exports = {
267299
// Encountered a non-callback prop after a callback prop
268300
context.report({
269301
node: memo,
270-
message: 'Callbacks must be listed after all other props'
302+
message: 'Callbacks must be listed after all other props',
303+
fix: generateFixerFunction(node, context, reservedList)
271304
});
272305
return memo;
273306
}
@@ -280,7 +313,8 @@ module.exports = {
280313
if (!currentValue && previousValue) {
281314
context.report({
282315
node: memo,
283-
message: 'Shorthand props must be listed before all other props'
316+
message: 'Shorthand props must be listed before all other props',
317+
fix: generateFixerFunction(node, context, reservedList)
284318
});
285319
return memo;
286320
}
@@ -293,7 +327,8 @@ module.exports = {
293327
if (currentValue && !previousValue) {
294328
context.report({
295329
node: memo,
296-
message: 'Shorthand props must be listed after all other props'
330+
message: 'Shorthand props must be listed after all other props',
331+
fix: generateFixerFunction(node, context, reservedList)
297332
});
298333
return memo;
299334
}

tests/lib/rules/jsx-sort-props.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -336,59 +336,75 @@ ruleTester.run('jsx-sort-props', rule, {
336336
{
337337
code: '<App key="key" b c="c" />',
338338
errors: [expectedShorthandLastError],
339-
options: reservedFirstWithShorthandLast
339+
options: reservedFirstWithShorthandLast,
340+
output: '<App key="key" c="c" b />'
340341
},
341342
{
342343
code: '<App ref="ref" key="key" isShorthand veryLastAttribute="yes" />',
343344
errors: [expectedError, expectedShorthandLastError],
344-
options: reservedFirstWithShorthandLast
345+
options: reservedFirstWithShorthandLast,
346+
output: '<App key="key" ref="ref" veryLastAttribute="yes" isShorthand />'
345347
},
346348
{
347349
code: '<App a z onFoo onBar />;',
348350
errors: [expectedError],
349-
options: callbacksLastArgs
351+
options: callbacksLastArgs,
352+
output: '<App a z onBar onFoo />;'
350353
},
351354
{
352355
code: '<App a onBar onFoo z />;',
353356
errors: [expectedCallbackError],
354-
options: callbacksLastArgs
357+
options: callbacksLastArgs,
358+
output: '<App a z onBar onFoo />;'
355359
},
356360
{
357361
code: '<App a="a" b />;',
358362
errors: [expectedShorthandFirstError],
359-
options: shorthandFirstArgs
363+
options: shorthandFirstArgs,
364+
output: '<App b a="a" />;'
360365
},
361366
{
362367
code: '<App z x a="a" />;',
363368
errors: [expectedError],
364-
options: shorthandFirstArgs
369+
options: shorthandFirstArgs,
370+
output: '<App x z a="a" />;'
365371
},
366372
{
367373
code: '<App b a="a" />;',
368374
errors: [expectedShorthandLastError],
369-
options: shorthandLastArgs
375+
options: shorthandLastArgs,
376+
output: '<App a="a" b />;'
370377
},
371378
{
372379
code: '<App a="a" onBar onFoo z x />;',
373380
errors: [shorthandAndCallbackLastArgs],
374-
options: shorthandLastArgs
381+
options: shorthandLastArgs,
382+
output: '<App a="a" onBar onFoo x z />;'
383+
},
384+
{
385+
code: '<App b a />;',
386+
errors: [expectedError],
387+
options: sortAlphabeticallyArgs,
388+
output: '<App a b />;'
375389
},
376-
{code: '<App b a />;', errors: [expectedError], options: sortAlphabeticallyArgs},
377390
// reservedFirst
378391
{
379392
code: '<App a key={1} />',
380393
options: reservedFirstAsBooleanArgs,
381-
errors: [expectedReservedFirstError]
394+
errors: [expectedReservedFirstError],
395+
output: '<App key={1} a />'
382396
},
383397
{
384398
code: '<div a dangerouslySetInnerHTML={{__html: "EPR"}} />',
385399
options: reservedFirstAsBooleanArgs,
386-
errors: [expectedReservedFirstError]
400+
errors: [expectedReservedFirstError],
401+
output: '<div dangerouslySetInnerHTML={{__html: "EPR"}} a />'
387402
},
388403
{
389404
code: '<App ref="r" key={2} b />',
390405
options: reservedFirstAsBooleanArgs,
391-
errors: [expectedError]
406+
errors: [expectedError],
407+
output: '<App key={2} ref="r" b />'
392408
},
393409
{
394410
code: '<App key={2} b a />',
@@ -411,12 +427,14 @@ ruleTester.run('jsx-sort-props', rule, {
411427
{
412428
code: '<App key={3} children={<App />} />',
413429
options: reservedFirstAsArrayArgs,
414-
errors: [expectedError]
430+
errors: [expectedError],
431+
output: '<App children={<App />} key={3} />'
415432
},
416433
{
417434
code: '<App z ref="r" />',
418435
options: reservedFirstWithNoSortAlphabeticallyArgs,
419-
errors: [expectedReservedFirstError]
436+
errors: [expectedReservedFirstError],
437+
output: '<App ref="r" z />'
420438
},
421439
{
422440
code: '<App key={4} />',

0 commit comments

Comments
 (0)