UNPKG

eslint-plugin-sonarjs

Version:
163 lines (162 loc) 6.57 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.allSortLike = void 0; exports.isJsonStringifyCall = isJsonStringifyCall; exports.getEqualityComparisonSibling = getEqualityComparisonSibling; exports.getChainedMethodCall = getChainedMethodCall; exports.isStringMapCall = isStringMapCall; exports.getJoinSeparator = getJoinSeparator; exports.getComparatorlessSortCallInfo = getComparatorlessSortCallInfo; exports.isPrimitiveSortReceiver = isPrimitiveSortReceiver; const ancestor_js_1 = require("../../helpers/ancestor.js"); const collection_js_1 = require("../../helpers/collection.js"); const ast_js_1 = require("../../helpers/ast.js"); const type_js_1 = require("../../helpers/type.js"); /** * Names of array sorting methods covered by S2871. * * This includes mutating sort-like methods such as `sort` and copying * sort-like methods such as `toSorted`. */ exports.allSortLike = new Set([...collection_js_1.sortLike, ...collection_js_1.copyingSortLike]); const equalityOperators = new Set(['==', '!=', '===', '!==']); /** * Checks whether a node is a direct `JSON.stringify(<expr>)` call. * * The match is intentionally narrow: exactly one argument, non-computed member * access, and the global-looking `JSON` identifier as receiver. It does not * match calls with replacer/space arguments, `JSON['stringify'](...)`, or * custom objects exposing a `stringify` method. */ function isJsonStringifyCall(node) { return (node !== null && (0, ast_js_1.isStaticMethodCall)(node, 'JSON', 'stringify') && node.arguments.length === 1); } /** * Returns the other side of an equality or inequality comparison. * * If `node` is not directly inside `==`, `!=`, `===`, or `!==`, there is no * comparison sibling to inspect and the function returns `null`. */ function getEqualityComparisonSibling(node) { const parent = (0, ancestor_js_1.getNodeParent)(node); if (parent.type !== 'BinaryExpression' || !equalityOperators.has(parent.operator)) { return null; } return parent.left === node ? parent.right : parent.left; } /** * Returns a method call chained directly after an expression. * * For `object` representing `value.sort()`, asking for `map` matches * `value.sort().map(...)` and returns the `map(...)` call expression. Computed * member access such as `value.sort()['map'](...)` is intentionally ignored. */ function getChainedMethodCall(object, methodName) { const member = (0, ancestor_js_1.getNodeParent)(object); if (member.type !== 'MemberExpression' || member.object !== object || member.computed || !(0, ast_js_1.isIdentifier)(member.property, methodName)) { return null; } const call = (0, ancestor_js_1.getNodeParent)(member); if (call.type !== 'CallExpression' || call.callee !== member) { return null; } return call; } /** * Checks whether a call expression is `array.map(String)`. * * This recognizes the explicit conversion to strings that makes subsequent * default sorting intentional in the false-positive patterns handled by S2871. * Computed access such as `array[map](String)` is intentionally excluded. */ function isStringMapCall(call) { return (call.arguments.length === 1 && (0, ast_js_1.isIdentifier)(call.arguments[0], 'String') && call.callee.type === 'MemberExpression' && !call.callee.computed && (0, ast_js_1.isIdentifier)(call.callee.property, 'map')); } /** * Extracts the separator from a `join` call when it is statically known. * * `join()` is treated as `join(',')`, matching JavaScript's default separator. * A single string literal argument is returned as-is. Other calls, such as * `join(separator)` or `join(',', extra)`, return `null` because the comparison * pattern can no longer prove both sides serialize identically. */ function getJoinSeparator(call) { if (call.arguments.length === 0) { return ','; } if (call.arguments.length === 1 && call.arguments[0].type === 'Literal' && typeof call.arguments[0].value === 'string') { return call.arguments[0].value; } return null; } /** * Extracts information from a comparator-less `sort()` or `toSorted()` call. * * The call must have no arguments, use a known sort-like method name, and be * invoked on an array-like receiver according to TypeScript type information. */ function getComparatorlessSortCallInfo(node, { sourceCode, services }) { if (node.type !== 'CallExpression') { return null; } const callExpr = node; if (callExpr.arguments.length !== 0) { return null; } if (callExpr.callee.type !== 'MemberExpression') { return null; } const callee = callExpr.callee; const text = sourceCode.getText(callee.property); if (!exports.allSortLike.has(text)) { return null; } const receiverType = (0, type_js_1.getTypeFromTreeNode)(callee.object, services); if (!(0, type_js_1.isArrayLikeType)(receiverType, services)) { return null; } return { methodName: text, receiver: callee.object, }; } /** * Checks whether a sort receiver is an array of primitive values with safe * normalization semantics for the S2871 false-positive patterns. * * Only number, bigint, string, and boolean arrays are accepted. Arrays of * objects, unknown values, unions, or custom wrappers still require S2871 to * report comparator-less sorting. */ function isPrimitiveSortReceiver(receiver, { services }) { const receiverType = (0, type_js_1.getTypeFromTreeNode)(receiver, services); return ((0, type_js_1.isNumberArray)(receiverType, services) || (0, type_js_1.isBigIntArray)(receiverType, services) || (0, type_js_1.isStringArray)(receiverType, services) || (0, type_js_1.isBooleanArray)(receiverType, services)); }