diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js index 7528c1905c..a0b83a4d06 100644 --- a/lib/rules/jsx-sort-props.js +++ b/lib/rules/jsx-sort-props.js @@ -27,22 +27,48 @@ function isReservedPropName(name, list) { return list.indexOf(name) >= 0; } -function propNameCompare(a, b, options) { - if (options.ignoreCase) { - a = a.toLowerCase(); - b = b.toLowerCase(); - } +function contextCompare(a, b, options) { + let aProp = propName(a); + let bProp = propName(b); + if (options.reservedFirst) { - const aIsReserved = isReservedPropName(a, options.reservedList); - const bIsReserved = isReservedPropName(b, options.reservedList); - if ((aIsReserved && bIsReserved) || (!aIsReserved && !bIsReserved)) { - return a.localeCompare(b); - } else if (aIsReserved && !bIsReserved) { + const aIsReserved = isReservedPropName(aProp, options.reservedList); + const bIsReserved = isReservedPropName(bProp, options.reservedList); + if (aIsReserved && !bIsReserved) { return -1; + } else if (!aIsReserved && bIsReserved) { + return 1; + } + } + + if (options.callbacksLast) { + const aIsCallback = isCallbackPropName(aProp); + const bIsCallback = isCallbackPropName(bProp); + if (aIsCallback && !bIsCallback) { + return 1; + } else if (!aIsCallback && bIsCallback) { + return -1; + } + } + + if (options.shorthandFirst || options.shorthandLast) { + const shorthandSign = options.shorthandFirst ? -1 : 1; + if (!a.value && b.value) { + return shorthandSign; + } else if (a.value && !b.value) { + return -shorthandSign; } - return 1; } - return a.localeCompare(b); + + if (options.noSortAlphabetically) { + return 0; + } + + if (options.ignoreCase) { + aProp = aProp.toLowerCase(); + bProp = bProp.toLowerCase(); + } + return aProp.localeCompare(bProp); } /** @@ -79,15 +105,21 @@ const generateFixerFunction = (node, context, reservedList) => { const attributes = node.attributes.slice(0); const configuration = context.options[0] || {}; const ignoreCase = configuration.ignoreCase || false; + const callbacksLast = configuration.callbacksLast || false; + const shorthandFirst = configuration.shorthandFirst || false; + const shorthandLast = configuration.shorthandLast || false; + const noSortAlphabetically = configuration.noSortAlphabetically || false; const reservedFirst = configuration.reservedFirst || false; // Sort props according to the context. Only supports ignoreCase. // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides), // we only consider groups of sortable attributes. + const options = {ignoreCase, callbacksLast, shorthandFirst, shorthandLast, + noSortAlphabetically, reservedFirst, reservedList}; const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes); const sortedAttributeGroups = sortableAttributeGroups.slice(0).map(group => group.slice(0).sort((a, b) => - propNameCompare(propName(a), propName(b), {ignoreCase, reservedFirst, reservedList}) + contextCompare(a, b, options) ) ); @@ -267,7 +299,8 @@ module.exports = { // Encountered a non-callback prop after a callback prop context.report({ node: memo, - message: 'Callbacks must be listed after all other props' + message: 'Callbacks must be listed after all other props', + fix: generateFixerFunction(node, context, reservedList) }); return memo; } @@ -280,7 +313,8 @@ module.exports = { if (!currentValue && previousValue) { context.report({ node: memo, - message: 'Shorthand props must be listed before all other props' + message: 'Shorthand props must be listed before all other props', + fix: generateFixerFunction(node, context, reservedList) }); return memo; } @@ -293,7 +327,8 @@ module.exports = { if (currentValue && !previousValue) { context.report({ node: memo, - message: 'Shorthand props must be listed after all other props' + message: 'Shorthand props must be listed after all other props', + fix: generateFixerFunction(node, context, reservedList) }); return memo; } diff --git a/tests/lib/rules/jsx-sort-props.js b/tests/lib/rules/jsx-sort-props.js index 082ef11f4e..a58dd57c91 100644 --- a/tests/lib/rules/jsx-sort-props.js +++ b/tests/lib/rules/jsx-sort-props.js @@ -336,59 +336,75 @@ ruleTester.run('jsx-sort-props', rule, { { code: '', errors: [expectedShorthandLastError], - options: reservedFirstWithShorthandLast + options: reservedFirstWithShorthandLast, + output: '' }, { code: '', errors: [expectedError, expectedShorthandLastError], - options: reservedFirstWithShorthandLast + options: reservedFirstWithShorthandLast, + output: '' }, { code: ';', errors: [expectedError], - options: callbacksLastArgs + options: callbacksLastArgs, + output: ';' }, { code: ';', errors: [expectedCallbackError], - options: callbacksLastArgs + options: callbacksLastArgs, + output: ';' }, { code: ';', errors: [expectedShorthandFirstError], - options: shorthandFirstArgs + options: shorthandFirstArgs, + output: ';' }, { code: ';', errors: [expectedError], - options: shorthandFirstArgs + options: shorthandFirstArgs, + output: ';' }, { code: ';', errors: [expectedShorthandLastError], - options: shorthandLastArgs + options: shorthandLastArgs, + output: ';' }, { code: ';', errors: [shorthandAndCallbackLastArgs], - options: shorthandLastArgs + options: shorthandLastArgs, + output: ';' + }, + { + code: ';', + errors: [expectedError], + options: sortAlphabeticallyArgs, + output: ';' }, - {code: ';', errors: [expectedError], options: sortAlphabeticallyArgs}, // reservedFirst { code: '', options: reservedFirstAsBooleanArgs, - errors: [expectedReservedFirstError] + errors: [expectedReservedFirstError], + output: '' }, { code: '
', options: reservedFirstAsBooleanArgs, - errors: [expectedReservedFirstError] + errors: [expectedReservedFirstError], + output: '
' }, { code: '', options: reservedFirstAsBooleanArgs, - errors: [expectedError] + errors: [expectedError], + output: '' }, { code: '', @@ -411,12 +427,14 @@ ruleTester.run('jsx-sort-props', rule, { { code: '} />', options: reservedFirstAsArrayArgs, - errors: [expectedError] + errors: [expectedError], + output: '} key={3} />' }, { code: '', options: reservedFirstWithNoSortAlphabeticallyArgs, - errors: [expectedReservedFirstError] + errors: [expectedReservedFirstError], + output: '' }, { code: '',