Skip to content

Commit 6d6f5bd

Browse files
committed
[New] jsx-sort-props: add locale option
Fixes #3002
1 parent 69ac0a0 commit 6d6f5bd

File tree

4 files changed

+47
-6
lines changed

4 files changed

+47
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1313
* [`no-did-mount-set-state`], [`no-did-update-set-state`]: no-op with react >= 16.3 ([#1754][] @ljharb)
1414
* [`jsx-sort-props`]: support multiline prop groups ([#3198][] @duhamelgm)
1515
* [`jsx-key`]: add `warnDuplicates` option to warn on duplicate jsx keys in an array ([#2614][] @ljharb)
16+
* [`jsx-sort-props`]: add `locale` option ([#3002][] @ljharb)
1617

1718
### Fixed
1819
* [`prop-types`], `propTypes`: add support for exported type inference ([#3163][] @vedadeepta)
@@ -58,6 +59,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
5859
[#3163]: https://github.com/yannickcr/eslint-plugin-react/pull/3163
5960
[#3160]: https://github.com/yannickcr/eslint-plugin-react/pull/3160
6061
[#3133]: https://github.com/yannickcr/eslint-plugin-react/pull/3133
62+
[#3002]: https://github.com/yannickcr/eslint-plugin-react/issues/3002
6163
[#2945]: https://github.com/yannickcr/eslint-plugin-react/issues/2945
6264
[#2921]: https://github.com/yannickcr/eslint-plugin-react/pull/2921
6365
[#2861]: https://github.com/yannickcr/eslint-plugin-react/issues/2861

docs/rules/jsx-sort-props.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Examples of **correct** code for this rule:
3333
"ignoreCase": <boolean>,
3434
"noSortAlphabetically": <boolean>,
3535
"reservedFirst": <boolean>|<array<string>>,
36+
"locale": "auto" | "any valid locale"
3637
}]
3738
...
3839
```
@@ -135,6 +136,12 @@ With `reservedFirst: ["key"]`, the following will **not** warn:
135136
<Hello key={'uuid'} name="John" ref="ref" />
136137
```
137138

139+
### `locale`
140+
141+
Defaults to `"auto"`, meaning, the locale of the current environment.
142+
143+
Any other string provided here may be passed to `String.prototype.localeCompare` - note that an unknown or invalid locale may throw an exception and crash.
144+
138145
## When Not To Use It
139146

140147
This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing props isn't a part of your coding standards, then you can leave this rule off.

lib/rules/jsx-sort-props.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,20 @@ function contextCompare(a, b, options) {
9898
return 0;
9999
}
100100

101+
const actualLocale = options.locale === 'auto' ? undefined : options.locale;
102+
101103
if (options.ignoreCase) {
102104
aProp = aProp.toLowerCase();
103105
bProp = bProp.toLowerCase();
104-
return aProp.localeCompare(bProp);
106+
return aProp.localeCompare(bProp, actualLocale);
105107
}
106108
if (aProp === bProp) {
107109
return 0;
108110
}
109-
return aProp < bProp ? -1 : 1;
111+
if (options.locale === 'auto') {
112+
return aProp < bProp ? -1 : 1;
113+
}
114+
return aProp.localeCompare(bProp, actualLocale);
110115
}
111116

112117
/**
@@ -149,6 +154,7 @@ const generateFixerFunction = (node, context, reservedList) => {
149154
const multiline = configuration.multiline || 'ignore';
150155
const noSortAlphabetically = configuration.noSortAlphabetically || false;
151156
const reservedFirst = configuration.reservedFirst || false;
157+
const locale = configuration.locale || 'auto';
152158

153159
// Sort props according to the context. Only supports ignoreCase.
154160
// Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
@@ -162,6 +168,7 @@ const generateFixerFunction = (node, context, reservedList) => {
162168
noSortAlphabetically,
163169
reservedFirst,
164170
reservedList,
171+
locale,
165172
};
166173
const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes);
167174
const sortedAttributeGroups = sortableAttributeGroups
@@ -305,6 +312,10 @@ module.exports = {
305312
reservedFirst: {
306313
type: ['array', 'boolean'],
307314
},
315+
locale: {
316+
type: 'string',
317+
default: 'auto',
318+
},
308319
},
309320
additionalProperties: false,
310321
}],
@@ -321,6 +332,7 @@ module.exports = {
321332
const reservedFirst = configuration.reservedFirst || false;
322333
const reservedFirstError = validateReservedFirstConfig(context, reservedFirst);
323334
let reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST;
335+
const locale = configuration.locale || 'auto';
324336

325337
return {
326338
JSXOpeningElement(node) {
@@ -431,8 +443,8 @@ module.exports = {
431443
if (
432444
!noSortAlphabetically
433445
&& (
434-
ignoreCase
435-
? previousPropName.localeCompare(currentPropName) > 0
446+
(ignoreCase || locale !== 'auto')
447+
? previousPropName.localeCompare(currentPropName, locale === 'auto' ? undefined : locale) > 0
436448
: previousPropName > currentPropName
437449
)
438450
) {

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// -----------------------------------------------------------------------------
1111

1212
const RuleTester = require('eslint').RuleTester;
13+
const semver = require('semver');
1314
const rule = require('../../../lib/rules/jsx-sort-props');
1415

1516
const parsers = require('../../helpers/parsers');
@@ -120,7 +121,7 @@ const multilineAndShorthandAndCallbackLastArgs = [
120121
];
121122

122123
ruleTester.run('jsx-sort-props', rule, {
123-
valid: parsers.all([
124+
valid: parsers.all([].concat(
124125
{ code: '<App />;' },
125126
{ code: '<App {...this.props} />;' },
126127
{ code: '<App a b c />;' },
@@ -276,7 +277,26 @@ ruleTester.run('jsx-sort-props', rule, {
276277
code: '<App key="key" c="c" b />',
277278
options: reservedFirstWithShorthandLast,
278279
},
279-
]),
280+
{
281+
code: `
282+
<RawFileField
283+
onChange={handleChange}
284+
onFileRemove={asMedia ? null : handleRemove}
285+
{...props}
286+
/>
287+
`,
288+
},
289+
semver.satisfies(process.version, '>= 13') ? {
290+
code: `
291+
<RawFileField
292+
onFileRemove={asMedia ? null : handleRemove}
293+
onChange={handleChange}
294+
{...props}
295+
/>
296+
`,
297+
options: [{ locale: 'sk-SK' }],
298+
} : []
299+
)),
280300
invalid: parsers.all([
281301
{
282302
code: '<App b a />;',

0 commit comments

Comments
 (0)