eslint-plugin-sonarjs
Version:
SonarJS rules for ESLint
100 lines (99 loc) • 4.39 kB
JavaScript
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
// https://sonarsource.github.io/rspec/#/rspec/S2871/javascript
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = void 0;
const index_js_1 = require("../helpers/index.js");
const meta_js_1 = require("./meta.js");
const compareNumberFunctionPlaceholder = '(a, b) => (a - b)';
const compareBigIntFunctionPlaceholder = [
'(a, b) => {',
' if (a < b) {',
' return -1;',
' } else if (a > b) {',
' return 1;',
' } else {',
' return 0;',
' }',
'}',
];
const languageSensitiveOrderPlaceholder = '(a, b) => a.localeCompare(b)';
exports.rule = {
meta: (0, index_js_1.generateMeta)(meta_js_1.meta, {
hasSuggestions: true,
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',
},
}),
create(context) {
const sourceCode = context.sourceCode;
const services = context.sourceCode.parserServices;
if (!(0, index_js_1.isRequiredParserServices)(services)) {
return {};
}
return {
'CallExpression[arguments.length=0][callee.type="MemberExpression"]': (call) => {
const { object, property: node } = call.callee;
const text = sourceCode.getText(node);
const type = (0, index_js_1.getTypeFromTreeNode)(object, services);
if ([...index_js_1.sortLike, ...index_js_1.copyingSortLike].includes(text) && (0, index_js_1.isArrayLikeType)(type, services)) {
const suggest = getSuggestions(call, type);
const messageId = getMessageId(type);
context.report({ node, suggest, messageId });
}
},
};
function getSuggestions(call, type) {
const suggestions = [];
if ((0, index_js_1.isNumberArray)(type, services)) {
suggestions.push({
messageId: 'suggestNumericOrder',
fix: fixer(call, compareNumberFunctionPlaceholder),
});
}
else if ((0, index_js_1.isBigIntArray)(type, services)) {
suggestions.push({
messageId: 'suggestNumericOrder',
fix: fixer(call, ...compareBigIntFunctionPlaceholder),
});
}
else if ((0, index_js_1.isStringArray)(type, services)) {
suggestions.push({
messageId: 'suggestLanguageSensitiveOrder',
fix: fixer(call, languageSensitiveOrderPlaceholder),
});
}
return suggestions;
}
function getMessageId(type) {
if ((0, index_js_1.isStringArray)(type, services)) {
return 'provideCompareFunctionForArrayOfStrings';
}
return 'provideCompareFunction';
}
function fixer(call, ...placeholder) {
const closingParenthesis = sourceCode.getLastToken(call, token => token.value === ')');
const indent = ' '.repeat(call.loc?.start.column);
const text = placeholder.join(`\n${indent}`);
return fixer => fixer.insertTextBefore(closingParenthesis, text);
}
},
};
;