Skip to content

Commit 8798690

Browse files
author
Mathis Girault
committed
adding esling rule
1 parent e4ce6f1 commit 8798690

File tree

2 files changed

+335
-0
lines changed

2 files changed

+335
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
3+
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
"use strict";
20+
21+
/** @type {import("eslint").Rule.RuleModule} */
22+
module.exports = {
23+
meta: {
24+
type: "suggestion",
25+
docs: {
26+
description: "Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback.",
27+
category: "eco-design",
28+
recommended: "warn",
29+
},
30+
messages: {
31+
avoidSizeInLoop: "Avoid getting the size/length of the collection in the loop or callback. Assign it to a variable before the loop/callback.",
32+
},
33+
schema: [],
34+
},
35+
create: function (context) {
36+
const SIZE_PROPERTIES = ["length", "size"]; // We only include static analysis on size and length properties at the moment
37+
const CALLBACK_METHODS = ["map", "forEach", "filter", "some", "every"];
38+
39+
/**
40+
* Checks if a node is a .length or .size property access (dot or bracket notation).
41+
* If you want to only match dot notation, keep !node.computed.
42+
*/
43+
function isSizeOrLengthMember(node) {
44+
return node.type === "MemberExpression" && SIZE_PROPERTIES.includes(node.property.name);
45+
}
46+
47+
/**
48+
* Recursively walk the AST node and report if .length or .size is accessed.
49+
*/
50+
function checkNodeRecursively(node) {
51+
if (!node) return;
52+
53+
if (isSizeOrLengthMember(node)) {
54+
context.report({ node, messageId: "avoidSizeInLoop" });
55+
return;
56+
}
57+
58+
for (const key in node) {
59+
// Prevents infinite recursion
60+
if (key === "parent") continue;
61+
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
62+
63+
// Recursively check on each child nodes
64+
const child = node[key];
65+
if (Array.isArray(child)) {
66+
child.forEach(checkNodeRecursively);
67+
} else if (child && typeof child.type === "string") {
68+
checkNodeRecursively(child);
69+
}
70+
}
71+
}
72+
73+
/**
74+
* Checks the callback function body of array methods like map, forEach, etc.
75+
*/
76+
function checkCallbackArg(node) {
77+
if (
78+
node.arguments &&
79+
node.arguments.length > 0 &&
80+
(node.arguments[0].type === "FunctionExpression" || node.arguments[0].type === "ArrowFunctionExpression")
81+
) {
82+
checkNodeRecursively(node.arguments[0].body);
83+
}
84+
}
85+
86+
return {
87+
ForStatement(node) {
88+
checkNodeRecursively(node.test);
89+
checkNodeRecursively(node.body);
90+
},
91+
WhileStatement(node) {
92+
checkNodeRecursively(node.test);
93+
checkNodeRecursively(node.body);
94+
},
95+
DoWhileStatement(node) {
96+
checkNodeRecursively(node.test);
97+
checkNodeRecursively(node.body);
98+
},
99+
ForInStatement(node) {
100+
checkNodeRecursively(node.body);
101+
},
102+
ForOfStatement(node) {
103+
checkNodeRecursively(node.body);
104+
},
105+
CallExpression(node) {
106+
if (
107+
node.callee &&
108+
node.callee.type === "MemberExpression" &&
109+
CALLBACK_METHODS.includes(node.callee.property.name)
110+
) {
111+
checkCallbackArg(node);
112+
}
113+
},
114+
};
115+
},
116+
};
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
3+
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
"use strict";
20+
21+
//------------------------------------------------------------------------------
22+
// Requirements
23+
//------------------------------------------------------------------------------
24+
25+
const rule = require("../../../lib/rules/getting-collection-size-in-collection");
26+
const RuleTester = require("eslint").RuleTester;
27+
28+
//------------------------------------------------------------------------------
29+
// Tests
30+
//------------------------------------------------------------------------------
31+
32+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
33+
const expectedError = {
34+
messageId: "avoidSizeInLoop",
35+
};
36+
37+
ruleTester.run("getting-collection-size-in-collection", rule, {
38+
valid: [
39+
// size/length assigned before loop, not used in loop
40+
`
41+
const n = arr.length;
42+
for (let i = 0; i < n; i++) {
43+
doSomething(arr[i]);
44+
}
45+
`,
46+
// unrelated property in loop condition
47+
`
48+
for (let i = 0; i < arr.customProp; i++) {
49+
doSomething();
50+
}
51+
`,
52+
// computed property (should not be flagged)
53+
`
54+
for (let i = 0; i < arr["length"]; i++) {
55+
doSomething();
56+
}
57+
`,
58+
// size/length used outside loop
59+
`
60+
let len = arr.length;
61+
let s = set.size;
62+
doSomething(len, s);
63+
`,
64+
// callback with no size/length
65+
`
66+
arr.forEach(item => {
67+
doSomething(item);
68+
});
69+
`,
70+
// unrelated method in callback
71+
`
72+
arr.map(a => a + 1);
73+
`,
74+
],
75+
76+
invalid: [
77+
// 1. In loop condition (test)
78+
{
79+
code: `
80+
for (let i = 0; i < arr.length; i++) {
81+
doSomething(arr[i]);
82+
}
83+
`,
84+
errors: [expectedError],
85+
},
86+
{
87+
code: `
88+
while (set.size > 0) {
89+
set.deleteOne();
90+
}
91+
`,
92+
errors: [expectedError],
93+
},
94+
{
95+
code: `
96+
do {
97+
process();
98+
} while (list.length !== 0);
99+
`,
100+
errors: [expectedError],
101+
},
102+
{
103+
code: `
104+
for (; arr.length > 0;) {
105+
arr.pop();
106+
}
107+
`,
108+
errors: [expectedError],
109+
},
110+
{
111+
code: `
112+
while (0 !== arr.length) {
113+
arr.pop();
114+
}
115+
`,
116+
errors: [expectedError],
117+
},
118+
119+
// 2. In loop body (direct)
120+
{
121+
code: `
122+
for (let i = 0; i < 5; i++) {
123+
arr.length;
124+
}
125+
`,
126+
errors: [expectedError],
127+
},
128+
{
129+
code: `
130+
while (true) {
131+
arr.size;
132+
}
133+
`,
134+
errors: [expectedError],
135+
},
136+
{
137+
code: `
138+
do {
139+
arr.length;
140+
} while (false);
141+
`,
142+
errors: [expectedError],
143+
},
144+
145+
// 3. In loop body (recursive/nested)
146+
{
147+
code: `
148+
for (let i = 0; i < 5; i++) {
149+
if (arr.length > 0) {
150+
doSomething();
151+
}
152+
}
153+
`,
154+
errors: [expectedError],
155+
},
156+
{
157+
code: `
158+
while (true) {
159+
function inner() {
160+
return arr.size;
161+
}
162+
}
163+
`,
164+
errors: [expectedError],
165+
},
166+
167+
// 4. As method call (arr.size())
168+
{
169+
code: `
170+
for (let i = 0; i < 5; i++) {
171+
arr.size();
172+
}
173+
`,
174+
errors: [expectedError],
175+
},
176+
{
177+
code: `
178+
while (true) {
179+
arr.length();
180+
}
181+
`,
182+
errors: [expectedError],
183+
},
184+
185+
// 5. In callback of array methods
186+
{
187+
code: `
188+
arr.forEach(item => {
189+
doSomething(arr.length);
190+
});
191+
`,
192+
errors: [expectedError],
193+
},
194+
{
195+
code: `
196+
arr.map(function(x) {
197+
return set.size;
198+
});
199+
`,
200+
errors: [expectedError],
201+
},
202+
{
203+
code: `
204+
arr.filter(x => arr.size());
205+
`,
206+
errors: [expectedError],
207+
},
208+
209+
// 6. Multiple in one loop
210+
{
211+
code: `
212+
for (let i = 0; i < arr.length; i++) {
213+
arr.size();
214+
}
215+
`,
216+
errors: [expectedError, expectedError],
217+
},
218+
],
219+
});

0 commit comments

Comments
 (0)