1 | /**
|
---|
2 | * @fileoverview enforce "for" loop update clause moving the counter in the right direction.(for-direction)
|
---|
3 | * @author Aladdin-ADD<hh_2013@foxmail.com>
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | //------------------------------------------------------------------------------
|
---|
9 | // Requirements
|
---|
10 | //------------------------------------------------------------------------------
|
---|
11 |
|
---|
12 | const { getStaticValue } = require("@eslint-community/eslint-utils");
|
---|
13 |
|
---|
14 | //------------------------------------------------------------------------------
|
---|
15 | // Rule Definition
|
---|
16 | //------------------------------------------------------------------------------
|
---|
17 |
|
---|
18 | /** @type {import('../shared/types').Rule} */
|
---|
19 | module.exports = {
|
---|
20 | meta: {
|
---|
21 | type: "problem",
|
---|
22 |
|
---|
23 | docs: {
|
---|
24 | description: "Enforce \"for\" loop update clause moving the counter in the right direction",
|
---|
25 | recommended: true,
|
---|
26 | url: "https://eslint.org/docs/latest/rules/for-direction"
|
---|
27 | },
|
---|
28 |
|
---|
29 | fixable: null,
|
---|
30 | schema: [],
|
---|
31 |
|
---|
32 | messages: {
|
---|
33 | incorrectDirection: "The update clause in this loop moves the variable in the wrong direction."
|
---|
34 | }
|
---|
35 | },
|
---|
36 |
|
---|
37 | create(context) {
|
---|
38 | const { sourceCode } = context;
|
---|
39 |
|
---|
40 | /**
|
---|
41 | * report an error.
|
---|
42 | * @param {ASTNode} node the node to report.
|
---|
43 | * @returns {void}
|
---|
44 | */
|
---|
45 | function report(node) {
|
---|
46 | context.report({
|
---|
47 | node,
|
---|
48 | messageId: "incorrectDirection"
|
---|
49 | });
|
---|
50 | }
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * check the right side of the assignment
|
---|
54 | * @param {ASTNode} update UpdateExpression to check
|
---|
55 | * @param {int} dir expected direction that could either be turned around or invalidated
|
---|
56 | * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear
|
---|
57 | */
|
---|
58 | function getRightDirection(update, dir) {
|
---|
59 | const staticValue = getStaticValue(update.right, sourceCode.getScope(update));
|
---|
60 |
|
---|
61 | if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) {
|
---|
62 | const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0
|
---|
63 |
|
---|
64 | return dir * sign;
|
---|
65 | }
|
---|
66 | return 0;
|
---|
67 | }
|
---|
68 |
|
---|
69 | /**
|
---|
70 | * check UpdateExpression add/sub the counter
|
---|
71 | * @param {ASTNode} update UpdateExpression to check
|
---|
72 | * @param {string} counter variable name to check
|
---|
73 | * @returns {int} if add return 1, if sub return -1, if nochange, return 0
|
---|
74 | */
|
---|
75 | function getUpdateDirection(update, counter) {
|
---|
76 | if (update.argument.type === "Identifier" && update.argument.name === counter) {
|
---|
77 | if (update.operator === "++") {
|
---|
78 | return 1;
|
---|
79 | }
|
---|
80 | if (update.operator === "--") {
|
---|
81 | return -1;
|
---|
82 | }
|
---|
83 | }
|
---|
84 | return 0;
|
---|
85 | }
|
---|
86 |
|
---|
87 | /**
|
---|
88 | * check AssignmentExpression add/sub the counter
|
---|
89 | * @param {ASTNode} update AssignmentExpression to check
|
---|
90 | * @param {string} counter variable name to check
|
---|
91 | * @returns {int} if add return 1, if sub return -1, if nochange, return 0
|
---|
92 | */
|
---|
93 | function getAssignmentDirection(update, counter) {
|
---|
94 | if (update.left.name === counter) {
|
---|
95 | if (update.operator === "+=") {
|
---|
96 | return getRightDirection(update, 1);
|
---|
97 | }
|
---|
98 | if (update.operator === "-=") {
|
---|
99 | return getRightDirection(update, -1);
|
---|
100 | }
|
---|
101 | }
|
---|
102 | return 0;
|
---|
103 | }
|
---|
104 |
|
---|
105 | return {
|
---|
106 | ForStatement(node) {
|
---|
107 |
|
---|
108 | if (node.test && node.test.type === "BinaryExpression" && node.update) {
|
---|
109 | for (const counterPosition of ["left", "right"]) {
|
---|
110 | if (node.test[counterPosition].type !== "Identifier") {
|
---|
111 | continue;
|
---|
112 | }
|
---|
113 |
|
---|
114 | const counter = node.test[counterPosition].name;
|
---|
115 | const operator = node.test.operator;
|
---|
116 | const update = node.update;
|
---|
117 |
|
---|
118 | let wrongDirection;
|
---|
119 |
|
---|
120 | if (operator === "<" || operator === "<=") {
|
---|
121 | wrongDirection = counterPosition === "left" ? -1 : 1;
|
---|
122 | } else if (operator === ">" || operator === ">=") {
|
---|
123 | wrongDirection = counterPosition === "left" ? 1 : -1;
|
---|
124 | } else {
|
---|
125 | return;
|
---|
126 | }
|
---|
127 |
|
---|
128 | if (update.type === "UpdateExpression") {
|
---|
129 | if (getUpdateDirection(update, counter) === wrongDirection) {
|
---|
130 | report(node);
|
---|
131 | }
|
---|
132 | } else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
|
---|
133 | report(node);
|
---|
134 | }
|
---|
135 | }
|
---|
136 | }
|
---|
137 | }
|
---|
138 | };
|
---|
139 | }
|
---|
140 | };
|
---|