diff --git a/CHANGELOG.md b/CHANGELOG.md index ba18b5def3..505cf3f659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed * [`require-default-props`]: fix config schema ([#3605][] @controversial) * [`jsx-curly-brace-presence`]: Revert [#3538][] due to issues with intended string type casting usage ([#3611][] @taozhou-glean) +* [`sort-prop-types`]: ensure sort-prop-types respects noSortAlphabetically ([#3610][] @caesar1030) [#3611]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3611 +[#3610]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3610 [#3605]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3605 ## [7.33.0] - 2023.07.19 diff --git a/lib/rules/sort-prop-types.js b/lib/rules/sort-prop-types.js index afe1e7fa99..56e67033c7 100644 --- a/lib/rules/sort-prop-types.js +++ b/lib/rules/sort-prop-types.js @@ -114,6 +114,7 @@ module.exports = { ignoreCase, requiredFirst, callbacksLast, + noSortAlphabetically, sortShapeProp ); } diff --git a/lib/util/propTypesSort.js b/lib/util/propTypesSort.js index d5c7a3e44b..3cfa1d0223 100644 --- a/lib/util/propTypesSort.js +++ b/lib/util/propTypesSort.js @@ -69,9 +69,10 @@ function getShapeProperties(node) { * @param {Boolean=} ignoreCase whether or not to ignore case when comparing the two elements. * @param {Boolean=} requiredFirst whether or not to sort required elements first. * @param {Boolean=} callbacksLast whether or not to sort callbacks after everything else. + * @param {Boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements. * @returns {Number} the sort order of the two elements. */ -function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) { +function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) { const aKey = String(astUtil.getKeyValue(context, a)); const bKey = String(astUtil.getKeyValue(context, b)); @@ -93,19 +94,23 @@ function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) { } } - if (ignoreCase) { - return aKey.localeCompare(bKey); - } + if (!noSortAlphabetically) { + if (ignoreCase) { + return aKey.localeCompare(bKey); + } - if (aKey < bKey) { - return -1; - } - if (aKey > bKey) { - return 1; + if (aKey < bKey) { + return -1; + } + if (aKey > bKey) { + return 1; + } } return 0; } +const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range + /** * Fixes sort order of prop types. * @@ -115,11 +120,20 @@ function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) { * @param {Boolean=} ignoreCase whether or not to ignore case when comparing the two elements. * @param {Boolean=} requiredFirst whether or not to sort required elements first. * @param {Boolean=} callbacksLast whether or not to sort callbacks after everything else. + * @param {Boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements. * @param {Boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape. * @returns {Object|*|{range, text}} the sort order of the two elements. */ -const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range -function fixPropTypesSort(fixer, context, declarations, ignoreCase, requiredFirst, callbacksLast, sortShapeProp) { +function fixPropTypesSort( + fixer, + context, + declarations, + ignoreCase, + requiredFirst, + callbacksLast, + noSortAlphabetically, + sortShapeProp +) { function sortInSource(allNodes, source) { const originalSource = source; const sourceCode = context.getSourceCode(); @@ -161,7 +175,7 @@ function fixPropTypesSort(fixer, context, declarations, ignoreCase, requiredFirs nodeGroups.forEach((nodes) => { const sortedAttributes = toSorted( nodes, - (a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast) + (a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) ); source = nodes.reduceRight((acc, attr, index) => { diff --git a/tests/lib/rules/sort-prop-types.js b/tests/lib/rules/sort-prop-types.js index c0382f45b0..d7417c005a 100644 --- a/tests/lib/rules/sort-prop-types.js +++ b/tests/lib/rules/sort-prop-types.js @@ -467,6 +467,19 @@ ruleTester.run('sort-prop-types', rule, { }; `, options: [{ sortShapeProp: true }], + }, + { + code: ` + var Component = createReactClass({ + propTypes: { + a: React.PropTypes.string, + c: React.PropTypes.string, + b: React.PropTypes.string, + onChange: React.PropTypes.func, + } + }); + `, + options: [{ callbacksLast: true, noSortAlphabetically: true }], } )), invalid: parsers.all([].concat( @@ -1886,6 +1899,37 @@ ruleTester.run('sort-prop-types', rule, { }, ], }, + { + code: ` + var Component = createReactClass({ + propTypes: { + onChange: React.PropTypes.func, + a: React.PropTypes.string, + c: React.PropTypes.string, + b: React.PropTypes.string, + } + }); + `, + output: ` + var Component = createReactClass({ + propTypes: { + a: React.PropTypes.string, + c: React.PropTypes.string, + b: React.PropTypes.string, + onChange: React.PropTypes.func, + } + }); + `, + options: [{ callbacksLast: true, noSortAlphabetically: true }], + errors: [ + { + messageId: 'callbackPropsLast', + line: 4, + column: 13, + type: 'Property', + }, + ], + }, semver.satisfies(eslintPkg.version, '> 3') ? { code: ` var First = createReactClass({ @@ -2148,6 +2192,64 @@ ruleTester.run('sort-prop-types', rule, { type: 'Property', }, ], + } : [], + semver.satisfies(eslintPkg.version, '> 3') ? { + code: ` + var Component = createReactClass({ + propTypes: { + /* onChange */ onChange: React.PropTypes.func, + /* a */ a: React.PropTypes.string, + /* c */ c: React.PropTypes.string, + /* b */ b: React.PropTypes.string, + } + }); + `, + output: ` + var Component = createReactClass({ + propTypes: { + /* a */ a: React.PropTypes.string, + /* c */ c: React.PropTypes.string, + /* b */ b: React.PropTypes.string, + /* onChange */ onChange: React.PropTypes.func, + } + }); + `, + options: [{ callbacksLast: true, noSortAlphabetically: true }], + errors: [ + { + messageId: 'callbackPropsLast', + line: 4, + }, + ], + } : [], + semver.satisfies(eslintPkg.version, '> 3') ? { + code: ` + var Component = createReactClass({ + propTypes: { + /* onChange */ onChange: React.PropTypes.func /* onChange */, + /* a */ a: React.PropTypes.string /* a */, + /* c */ c: React.PropTypes.string /* c */, + /* b */ b: React.PropTypes.string /* b */, + } + }); + `, + output: ` + var Component = createReactClass({ + propTypes: { + /* a */ a: React.PropTypes.string /* a */, + /* c */ c: React.PropTypes.string /* c */, + /* b */ b: React.PropTypes.string /* b */, + /* onChange */ onChange: React.PropTypes.func /* onChange */, + } + }); + `, + options: [{ callbacksLast: true, noSortAlphabetically: true }], + errors: [ + { + messageId: 'callbackPropsLast', + line: 4, + }, + ], } : [] )), });