UNPKG

eslint-plugin-sonarjs

Version:
172 lines (171 loc) 6.4 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/S888/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 allEqualityOperators = ['!=', '==', '!==', '===']; const notEqualOperators = ['!==', '!=']; const plusMinusOperators = ['+=', '-=']; exports.rule = { meta: (0, index_js_1.generateMeta)(meta_js_1.meta, { messages: { replaceOperator: "Replace '{{operator}}' operator with one of '<=', '>=', '<', or '>' comparison operators.", }, }), create(context) { return { ForStatement: (node) => { const forStatement = node; if (!forStatement.test || !forStatement.update) { return; } const completeForStatement = node; const condition = completeForStatement.test; if (isEquality(condition) && isUpdateIncDec(completeForStatement.update) && !isException(completeForStatement, context)) { context.report({ messageId: 'replaceOperator', data: { operator: condition.operator, }, node: condition, }); } }, }; }, }; function isEquality(expression) { return (expression.type === 'BinaryExpression' && allEqualityOperators.includes(expression.operator)); } function isUpdateIncDec(expression) { if (isIncDec(expression) || expression.type === 'UpdateExpression') { return true; } else if (expression.type === 'SequenceExpression') { return expression.expressions.every(isUpdateIncDec); } return false; } function isIncDec(expression) { return (expression.type === 'AssignmentExpression' && plusMinusOperators.includes(expression.operator)); } function isException(forStatement, context) { return (isNontrivialConditionException(forStatement) || isTrivialIteratorException(forStatement, context)); } function isNontrivialConditionException(forStatement) { //If we reach this point, we know that test is an equality kind const condition = forStatement.test; const counters = []; collectCounters(forStatement.update, counters); return condition.left.type !== 'Identifier' || !counters.includes(condition.left.name); } function collectCounters(expression, counters) { let counter = undefined; if (isIncDec(expression)) { counter = expression.left; } else if (expression.type === 'UpdateExpression') { counter = expression.argument; } else if (expression.type === 'SequenceExpression') { expression.expressions.forEach(e => collectCounters(e, counters)); } if (counter && counter.type === 'Identifier') { counters.push(counter.name); } } function isTrivialIteratorException(forStatement, context) { const init = forStatement.init; const condition = forStatement.test; if (init && isNotEqual(condition)) { const updatedByOne = checkForUpdateByOne(forStatement.update, forStatement.body, context); if (updatedByOne !== 0) { const beginValue = getValue(init); const endValue = getValue(condition); return (beginValue !== undefined && endValue !== undefined && updatedByOne === Math.sign(endValue - beginValue)); } } return false; } function isNotEqual(node) { return node.type === 'BinaryExpression' && notEqualOperators.includes(node.operator); } function checkForUpdateByOne(update, loopBody, context) { if (isUpdateByOne(update, loopBody, context)) { if (update.operator === '++' || update.operator === '+=') { return +1; } if (update.operator === '--' || update.operator === '-=') { return -1; } } return 0; } function isUpdateByOne(update, loopBody, context) { return ((update.type === 'UpdateExpression' && !isUsedInsideBody(update.argument, loopBody, context)) || (isUpdateOnOneWithAssign(update) && !isUsedInsideBody(update.left, loopBody, context))); } function isUsedInsideBody(id, loopBody, context) { if (id.type === 'Identifier') { const variable = (0, index_js_1.getVariableFromName)(context, id.name, id); const bodyRange = loopBody.range; if (variable && bodyRange) { return variable.references.some(ref => isInBody(ref.identifier, bodyRange)); } } return false; } function isInBody(id, bodyRange) { return id?.range && id.range[0] > bodyRange[0] && id.range[1] < bodyRange[1]; } function getValue(node) { if (isNotEqual(node)) { return getInteger(node.right); } else if (isOneVarDeclaration(node)) { const variable = node.declarations[0]; return getInteger(variable.init); } else if (node.type === 'AssignmentExpression') { return getInteger(node.right); } return undefined; } function getInteger(node) { if (node && node.type === 'Literal' && typeof node.value === 'number') { return node.value; } return undefined; } function isOneVarDeclaration(node) { return node.type === 'VariableDeclaration' && node.declarations.length === 1; } function isUpdateOnOneWithAssign(expression) { if (isIncDec(expression)) { const right = expression.right; return right.type === 'Literal' && right.value === 1; } return false; }