UNPKG

eslint-plugin-sonarjs

Version:
192 lines (191 loc) 8.54 kB
"use strict"; /* * 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/S2234/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"); exports.rule = { meta: (0, index_js_1.generateMeta)(meta_js_1.meta, undefined, true), create(context) { const services = context.sourceCode.parserServices; const canResolveType = (0, index_js_1.isRequiredParserServices)(services); function checkArguments(functionCall) { const resolvedFunction = resolveFunctionDeclaration(functionCall); if (!resolvedFunction) { return; } const { params: functionParameters, declaration: functionDeclaration } = resolvedFunction; const argumentNames = functionCall.arguments.map(arg => { const argument = arg; return argument.type === 'Identifier' ? argument.name : undefined; }); for (let argumentIndex = 0; argumentIndex < argumentNames.length; argumentIndex++) { const argumentName = argumentNames[argumentIndex]; if (argumentName) { const swappedArgumentName = getSwappedArgumentName(argumentNames, functionParameters, argumentName, argumentIndex, functionCall); if (swappedArgumentName && !areComparedArguments([argumentName, swappedArgumentName], functionCall)) { raiseIssue(argumentName, swappedArgumentName, functionDeclaration, functionCall); return; } } } } function areComparedArguments(argumentNames, node) { function getName(node) { switch (node.type) { case 'Identifier': return node.name; case 'CallExpression': return getName(node.callee); case 'MemberExpression': return getName(node.object); default: return undefined; } } function checkComparedArguments(lhs, rhs) { return ([lhs, rhs].map(getName).filter(name => name && argumentNames.includes(name)).length === argumentNames.length); } const maybeIfStmt = context.sourceCode .getAncestors(node) .reverse() .find(ancestor => ancestor.type === 'IfStatement'); if (maybeIfStmt) { const { test } = maybeIfStmt; switch (test.type) { case 'BinaryExpression': { const binExpr = test; if (['==', '!=', '===', '!==', '<', '<=', '>', '>='].includes(binExpr.operator)) { const { left: lhs, right: rhs } = binExpr; return checkComparedArguments(lhs, rhs); } break; } case 'CallExpression': { const callExpr = test; if (callExpr.arguments.length === 1 && callExpr.callee.type === 'MemberExpression') { const [lhs, rhs] = [callExpr.callee.object, callExpr.arguments[0]]; return checkComparedArguments(lhs, rhs); } break; } } } return false; } function resolveFunctionDeclaration(node) { if (canResolveType) { return resolveFromTSSignature(node); } let functionDeclaration = null; if ((0, index_js_1.isFunctionNode)(node.callee)) { functionDeclaration = node.callee; } else if (node.callee.type === 'Identifier') { functionDeclaration = (0, index_js_1.resolveFromFunctionReference)(context, node.callee); } if (!functionDeclaration) { return null; } return { params: extractFunctionParameters(functionDeclaration), declaration: functionDeclaration, }; } function resolveFromTSSignature(node) { const signature = (0, index_js_1.getSignatureFromCallee)(node, services); if (signature?.declaration) { return { params: signature.parameters.map(param => param.name), declaration: services.tsNodeToESTreeNodeMap.get(signature.declaration), }; } return null; } function getSwappedArgumentName(argumentNames, functionParameters, argumentName, argumentIndex, node) { const indexInFunctionDeclaration = functionParameters.findIndex(functionParameterName => functionParameterName === argumentName); if (indexInFunctionDeclaration >= 0 && indexInFunctionDeclaration !== argumentIndex) { const potentiallySwappedArgument = argumentNames[indexInFunctionDeclaration]; if (potentiallySwappedArgument && potentiallySwappedArgument === functionParameters[argumentIndex] && haveCompatibleTypes(node.arguments[argumentIndex], node.arguments[indexInFunctionDeclaration])) { return potentiallySwappedArgument; } } return null; } function haveCompatibleTypes(arg1, arg2) { if (canResolveType) { const type1 = normalizeType((0, index_js_1.getTypeAsString)(arg1, services)); const type2 = normalizeType((0, index_js_1.getTypeAsString)(arg2, services)); return type1 === type2; } return true; } function raiseIssue(arg1, arg2, functionDeclaration, node) { (0, index_js_1.report)(context, { message: `Arguments '${arg1}' and '${arg2}' have the same names but not the same order as the function parameters.`, loc: getParametersClauseLocation(node.arguments), }, getSecondaryLocations(functionDeclaration)); } return { NewExpression: (node) => { checkArguments(node); }, CallExpression: (node) => { checkArguments(node); }, }; }, }; function extractFunctionParameters(functionDeclaration) { return functionDeclaration.params.map(param => { const identifiers = (0, index_js_1.resolveIdentifiers)(param); if (identifiers.length === 1 && identifiers[0]) { return identifiers[0].name; } return undefined; }); } function getSecondaryLocations(functionDeclaration) { if (functionDeclaration?.params && functionDeclaration.params.length > 0) { const { start, end } = getParametersClauseLocation(functionDeclaration.params); return [(0, index_js_1.toSecondaryLocation)({ loc: { start, end } }, 'Formal parameters')]; } return []; } function getParametersClauseLocation(parameters) { const firstParam = parameters[0]; const lastParam = parameters[parameters.length - 1]; return { start: firstParam.loc.start, end: lastParam.loc.end }; } function normalizeType(typeAsString) { switch (typeAsString) { case 'String': return 'string'; case 'Boolean': return 'boolean'; case 'Number': return 'number'; default: return typeAsString; } }