/** * @fileoverview enforce `for` loop update clause moving the counter in the right direction.(for-direction) * @author Aladdin-ADD */ 'use strict'; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const { getStaticValue } = require('@eslint-community/eslint-utils'); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { type: 'problem', docs: { description: 'Enforce `for` loop update clause moving the counter in the right direction', recommended: true, url: 'https://eslint.org/docs/latest/rules/for-direction', }, fixable: null, schema: [], messages: { incorrectDirection: 'The update clause in this loop moves the variable in the wrong direction.', }, }, create(context) { const { sourceCode } = context; /** * report an error. * @param {ASTNode} node the node to report. * @returns {void} */ function report(node) { context.report({ node, messageId: 'incorrectDirection', }); } /** * check the right side of the assignment * @param {ASTNode} update UpdateExpression to check * @param {int} dir expected direction that could either be turned around or invalidated * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear */ function getRightDirection(update, dir) { const staticValue = getStaticValue( update.right, sourceCode.getScope(update) ); if ( staticValue && ['bigint', 'boolean', 'number'].includes(typeof staticValue.value) ) { const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0 return dir * sign; } return 0; } /** * check UpdateExpression add/sub the counter * @param {ASTNode} update UpdateExpression to check * @param {string} counter variable name to check * @returns {int} if add return 1, if sub return -1, if nochange, return 0 */ function getUpdateDirection(update, counter) { if ( update.argument.type === 'Identifier' && update.argument.name === counter ) { if (update.operator === '++') { return 1; } if (update.operator === '--') { return -1; } } return 0; } /** * check AssignmentExpression add/sub the counter * @param {ASTNode} update AssignmentExpression to check * @param {string} counter variable name to check * @returns {int} if add return 1, if sub return -1, if nochange, return 0 */ function getAssignmentDirection(update, counter) { if (update.left.name === counter) { if (update.operator === '+=') { return getRightDirection(update, 1); } if (update.operator === '-=') { return getRightDirection(update, -1); } } return 0; } return { ForStatement(node) { if (node.test && node.test.type === 'BinaryExpression' && node.update) { for (const counterPosition of ['left', 'right']) { if (node.test[counterPosition].type !== 'Identifier') { continue; } const counter = node.test[counterPosition].name; const operator = node.test.operator; const update = node.update; let wrongDirection; if (operator === '<' || operator === '<=') { wrongDirection = counterPosition === 'left' ? -1 : 1; } else if (operator === '>' || operator === '>=') { wrongDirection = counterPosition === 'left' ? 1 : -1; } else { return; } if (update.type === 'UpdateExpression') { if (getUpdateDirection(update, counter) === wrongDirection) { report(node); } } else if ( update.type === 'AssignmentExpression' && getAssignmentDirection(update, counter) === wrongDirection ) { report(node); } } } }, }; }, };