diff --git a/packages/jsts/src/rules/S2871/rule.ts b/packages/jsts/src/rules/S2871/rule.ts index 3164425039..bfc97dca9d 100644 --- a/packages/jsts/src/rules/S2871/rule.ts +++ b/packages/jsts/src/rules/S2871/rule.ts @@ -53,6 +53,8 @@ export const rule: Rule.RuleModule = { messages: { provideCompareFunction: 'Provide a compare function to avoid sorting elements alphabetically.', + provideCompareFunctionForArrayOfStrings: + 'Provide a compare function that depends on "String.localeCompare", to reliably sort elements alphabetically.', suggestNumericOrder: 'Add a comparator function to sort in ascending order', suggestLanguageSensitiveOrder: 'Add a comparator function to sort in ascending language-sensitive order', @@ -75,7 +77,8 @@ export const rule: Rule.RuleModule = { if ([...sortLike, ...copyingSortLike].includes(text) && isArrayLikeType(type, services)) { const suggest = getSuggestions(call, type); - context.report({ node, suggest, messageId: 'provideCompareFunction' }); + const messageId = getMessageId(type); + context.report({ node, suggest, messageId }); } }, }; @@ -101,6 +104,14 @@ export const rule: Rule.RuleModule = { return suggestions; } + function getMessageId(type: ts.Type) { + if (isStringArray(type, services)) { + return 'provideCompareFunctionForArrayOfStrings'; + } + + return 'provideCompareFunction'; + } + function fixer(call: estree.CallExpression, ...placeholder: string[]): Rule.ReportFixer { const closingParenthesis = sourceCode.getLastToken(call, token => token.value === ')')!; const indent = ' '.repeat(call.loc?.start.column!); diff --git a/packages/jsts/src/rules/S2871/unit.test.ts b/packages/jsts/src/rules/S2871/unit.test.ts index 78b155f1fe..7283a31f8a 100644 --- a/packages/jsts/src/rules/S2871/unit.test.ts +++ b/packages/jsts/src/rules/S2871/unit.test.ts @@ -606,6 +606,8 @@ ruleTester.run( code: 'const array = ["foo", "bar"]; const sortedArray = array.toSorted();', errors: [ { + message: + 'Provide a compare function that depends on "String.localeCompare", to reliably sort elements alphabetically.', suggestions: [ { desc: 'Add a comparator function to sort in ascending language-sensitive order', diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S2871.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S2871.html index e1af336493..366f99b374 100644 --- a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S2871.html +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S2871.html @@ -22,8 +22,9 @@

Why is this an issue?

console.log(numbers); // Output: [1, 2, 5, 10, 30]

Even to sort strings, the default sort order may give unexpected results. Not only does it not support localization, it also doesn’t fully support -Unicode, as it only considers UTF-16 code units. For example, in the code below, "eΔ" is surprisingly before and after -"éΔ".

+Unicode, as it only considers UTF-16 code units. For example, in the code below, "eΔ" is surprisingly before and after "éΔ". +To guarantee that the sorting is reliable and remains as such in the long run, it is necessary to provide a compare function that is both locale and +Unicode aware - typically String.localeCompare.

 const code1 = '\u00e9\u0394'; // "éΔ"
 const code2 = '\u0065\u0301\u0394'; // "éΔ" using Unicode combining marks