UNPKG

eslint-plugin-sonarjs

Version:
105 lines (104 loc) 4.39 kB
"use strict"; /* * SonarQube JavaScript Plugin * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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/ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.isSortUsedInStringJoinComparison = isSortUsedInStringJoinComparison; const ast_js_1 = require("../../helpers/ast.js"); const type_js_1 = require("../../helpers/type.js"); const helpers_js_1 = require("./helpers.js"); /** * False-positive shapes: * a.slice().sort().map(String).join(',') === b.slice().sort().map(String).join(',') * a.toSorted().map(String).join(',') !== b.toSorted().map(String).join(',') * a.sort().map(String).join() == b.sort().map(String).join() * * What can vary: * - `sort()` vs `toSorted()`, as long as both sides use the same sort family * - the equality operator (`==`, `!=`, `===`, `!==`) * - the join separator, as long as both sides use the same safe one * * Here the sort call is only part of a normalization pipeline used to compare * two numeric arrays after converting both sides to the same canonical string. */ function isSortUsedInStringJoinComparison(call, ruleContext) { const chainRoot = getEnclosingJoinCall(call); if (chainRoot === null) { return false; } const callInfo = getStringJoinSortChainInfo(chainRoot, ruleContext); const siblingInfo = getStringJoinSortChainInfo((0, helpers_js_1.getEqualityComparisonSibling)(chainRoot), ruleContext); return (callInfo !== null && siblingInfo !== null && callInfo.methodName === siblingInfo.methodName && callInfo.joinSeparator === siblingInfo.joinSeparator && isSafeNumericJoinSeparator(callInfo.joinSeparator) && isNumericSortReceiver(callInfo.receiver, ruleContext) && isNumericSortReceiver(siblingInfo.receiver, ruleContext)); } function isSafeNumericJoinSeparator(separator) { return separator.length > 0 && !/[0-9+\-.eEn]/.test(separator); } function isNumericSortReceiver(receiver, ruleContext) { const receiverType = (0, type_js_1.getTypeFromTreeNode)(receiver, ruleContext.services); return ((0, type_js_1.isNumberArray)(receiverType, ruleContext.services) || (0, type_js_1.isBigIntArray)(receiverType, ruleContext.services)); } /** * Climbs the `sort() -> map(...) -> join(...)` chain from a comparator-less * sort call to the enclosing `join(...)` call — the root of the serialization * pipeline and the operand of the comparison. * * No shape validation happens here on purpose: `getStringJoinSortChainInfo` is * the single place that decides whether a `join(...)` call is an accepted * normalization chain, so both comparison sides are validated identically. */ function getEnclosingJoinCall(sortCall) { const mapCall = (0, helpers_js_1.getChainedMethodCall)(sortCall, 'map'); if (mapCall === null) { return null; } return (0, helpers_js_1.getChainedMethodCall)(mapCall, 'join'); } function getStringJoinSortChainInfo(node, ruleContext) { if (node?.type !== 'CallExpression') { return null; } if (node.callee.type !== 'MemberExpression' || node.callee.computed || !(0, ast_js_1.isIdentifier)(node.callee.property, 'join')) { return null; } const joinSeparator = (0, helpers_js_1.getJoinSeparator)(node); if (joinSeparator === null) { return null; } const mapCall = node.callee.object; if (mapCall.type !== 'CallExpression' || !(0, helpers_js_1.isStringMapCall)(mapCall)) { return null; } if (mapCall.callee.type !== 'MemberExpression') { return null; } const sortInfo = (0, helpers_js_1.getComparatorlessSortCallInfo)(mapCall.callee.object, ruleContext); if (sortInfo === null) { return null; } return { ...sortInfo, joinSeparator, }; }