From 8798690ce5c05a8b45fa21025005473b56afa939 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Tue, 20 May 2025 17:26:08 +0200 Subject: [PATCH 1/8] adding esling rule --- .../getting-collection-size-in-collection.js | 116 ++++++++++ .../getting-collection-size-in-collection.js | 219 ++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 eslint-plugin/lib/rules/getting-collection-size-in-collection.js create mode 100644 eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js diff --git a/eslint-plugin/lib/rules/getting-collection-size-in-collection.js b/eslint-plugin/lib/rules/getting-collection-size-in-collection.js new file mode 100644 index 0000000..22e6a7a --- /dev/null +++ b/eslint-plugin/lib/rules/getting-collection-size-in-collection.js @@ -0,0 +1,116 @@ +/* + * creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs + * Copyright © 2023 Green Code Initiative (https://green-code-initiative.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +"use strict"; + +/** @type {import("eslint").Rule.RuleModule} */ +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback.", + category: "eco-design", + recommended: "warn", + }, + messages: { + avoidSizeInLoop: "Avoid getting the size/length of the collection in the loop or callback. Assign it to a variable before the loop/callback.", + }, + schema: [], + }, + create: function (context) { + const SIZE_PROPERTIES = ["length", "size"]; // We only include static analysis on size and length properties at the moment + const CALLBACK_METHODS = ["map", "forEach", "filter", "some", "every"]; + + /** + * Checks if a node is a .length or .size property access (dot or bracket notation). + * If you want to only match dot notation, keep !node.computed. + */ + function isSizeOrLengthMember(node) { + return node.type === "MemberExpression" && SIZE_PROPERTIES.includes(node.property.name); + } + + /** + * Recursively walk the AST node and report if .length or .size is accessed. + */ + function checkNodeRecursively(node) { + if (!node) return; + + if (isSizeOrLengthMember(node)) { + context.report({ node, messageId: "avoidSizeInLoop" }); + return; + } + + for (const key in node) { + // Prevents infinite recursion + if (key === "parent") continue; + if (!Object.prototype.hasOwnProperty.call(node, key)) continue; + + // Recursively check on each child nodes + const child = node[key]; + if (Array.isArray(child)) { + child.forEach(checkNodeRecursively); + } else if (child && typeof child.type === "string") { + checkNodeRecursively(child); + } + } + } + + /** + * Checks the callback function body of array methods like map, forEach, etc. + */ + function checkCallbackArg(node) { + if ( + node.arguments && + node.arguments.length > 0 && + (node.arguments[0].type === "FunctionExpression" || node.arguments[0].type === "ArrowFunctionExpression") + ) { + checkNodeRecursively(node.arguments[0].body); + } + } + + return { + ForStatement(node) { + checkNodeRecursively(node.test); + checkNodeRecursively(node.body); + }, + WhileStatement(node) { + checkNodeRecursively(node.test); + checkNodeRecursively(node.body); + }, + DoWhileStatement(node) { + checkNodeRecursively(node.test); + checkNodeRecursively(node.body); + }, + ForInStatement(node) { + checkNodeRecursively(node.body); + }, + ForOfStatement(node) { + checkNodeRecursively(node.body); + }, + CallExpression(node) { + if ( + node.callee && + node.callee.type === "MemberExpression" && + CALLBACK_METHODS.includes(node.callee.property.name) + ) { + checkCallbackArg(node); + } + }, + }; + }, +}; diff --git a/eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js b/eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js new file mode 100644 index 0000000..6a29df6 --- /dev/null +++ b/eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js @@ -0,0 +1,219 @@ +/* + * creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs + * Copyright © 2023 Green Code Initiative (https://green-code-initiative.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/getting-collection-size-in-collection"); +const RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const expectedError = { + messageId: "avoidSizeInLoop", +}; + +ruleTester.run("getting-collection-size-in-collection", rule, { + valid: [ + // size/length assigned before loop, not used in loop + ` + const n = arr.length; + for (let i = 0; i < n; i++) { + doSomething(arr[i]); + } + `, + // unrelated property in loop condition + ` + for (let i = 0; i < arr.customProp; i++) { + doSomething(); + } + `, + // computed property (should not be flagged) + ` + for (let i = 0; i < arr["length"]; i++) { + doSomething(); + } + `, + // size/length used outside loop + ` + let len = arr.length; + let s = set.size; + doSomething(len, s); + `, + // callback with no size/length + ` + arr.forEach(item => { + doSomething(item); + }); + `, + // unrelated method in callback + ` + arr.map(a => a + 1); + `, + ], + + invalid: [ + // 1. In loop condition (test) + { + code: ` + for (let i = 0; i < arr.length; i++) { + doSomething(arr[i]); + } + `, + errors: [expectedError], + }, + { + code: ` + while (set.size > 0) { + set.deleteOne(); + } + `, + errors: [expectedError], + }, + { + code: ` + do { + process(); + } while (list.length !== 0); + `, + errors: [expectedError], + }, + { + code: ` + for (; arr.length > 0;) { + arr.pop(); + } + `, + errors: [expectedError], + }, + { + code: ` + while (0 !== arr.length) { + arr.pop(); + } + `, + errors: [expectedError], + }, + + // 2. In loop body (direct) + { + code: ` + for (let i = 0; i < 5; i++) { + arr.length; + } + `, + errors: [expectedError], + }, + { + code: ` + while (true) { + arr.size; + } + `, + errors: [expectedError], + }, + { + code: ` + do { + arr.length; + } while (false); + `, + errors: [expectedError], + }, + + // 3. In loop body (recursive/nested) + { + code: ` + for (let i = 0; i < 5; i++) { + if (arr.length > 0) { + doSomething(); + } + } + `, + errors: [expectedError], + }, + { + code: ` + while (true) { + function inner() { + return arr.size; + } + } + `, + errors: [expectedError], + }, + + // 4. As method call (arr.size()) + { + code: ` + for (let i = 0; i < 5; i++) { + arr.size(); + } + `, + errors: [expectedError], + }, + { + code: ` + while (true) { + arr.length(); + } + `, + errors: [expectedError], + }, + + // 5. In callback of array methods + { + code: ` + arr.forEach(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.map(function(x) { + return set.size; + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.filter(x => arr.size()); + `, + errors: [expectedError], + }, + + // 6. Multiple in one loop + { + code: ` + for (let i = 0; i < arr.length; i++) { + arr.size(); + } + `, + errors: [expectedError, expectedError], + }, + ], +}); From 49cf57199dead19e3eb867a24dafe14e87d75a39 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Tue, 20 May 2025 18:23:49 +0200 Subject: [PATCH 2/8] additionnal changes for sonar integration --- .../avoid-getting-size-collection-in-loop.md | 22 +++++++++++ ... avoid-getting-size-collection-in-loop.js} | 0 ... avoid-getting-size-collection-in-loop.js} | 4 +- .../creedengo/javascript/CheckList.java | 1 + .../AvoidGettingSizeCollectionInLoop.java | 38 +++++++++++++++++++ .../profiles/javascript_profile.json | 1 + .../avoid-getting-size-collection-in-loop.js | 10 +++++ 7 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md rename eslint-plugin/lib/rules/{getting-collection-size-in-collection.js => avoid-getting-size-collection-in-loop.js} (100%) rename eslint-plugin/tests/lib/rules/{getting-collection-size-in-collection.js => avoid-getting-size-collection-in-loop.js} (96%) create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidGettingSizeCollectionInLoop.java create mode 100644 test-project/src/avoid-getting-size-collection-in-loop.js diff --git a/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md b/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md new file mode 100644 index 0000000..fadcefb --- /dev/null +++ b/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md @@ -0,0 +1,22 @@ +# Should avoid getting the collection size in loop + +## Why is this an issue ? + +Accessing the size or length of a collection (such as arrays, maps, or sets) inside a loop can lead to unnecessary performance overhead, especially if the collection size is recalculated on each iteration. This rule warns when the size of a collection is accessed within a loop condition or body, encouraging developers to cache the size before the loop. + +### Examples of **incorrect** code + +```javascript +for (let i = 0; i < array.length; i++) { + doSomething(array[i]); +} +``` + +### Examples of **correct** code + +```javascript +const len = array.length; +for (let i = 0; i < len; i++) { + doSomething(array[i]); +} +``` diff --git a/eslint-plugin/lib/rules/getting-collection-size-in-collection.js b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js similarity index 100% rename from eslint-plugin/lib/rules/getting-collection-size-in-collection.js rename to eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js diff --git a/eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js similarity index 96% rename from eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js rename to eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js index 6a29df6..ce84c4b 100644 --- a/eslint-plugin/tests/lib/rules/getting-collection-size-in-collection.js +++ b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js @@ -22,7 +22,7 @@ // Requirements //------------------------------------------------------------------------------ -const rule = require("../../../lib/rules/getting-collection-size-in-collection"); +const rule = require("../../../lib/rules/avoid-getting-size-collection-in-loop"); const RuleTester = require("eslint").RuleTester; //------------------------------------------------------------------------------ @@ -34,7 +34,7 @@ const expectedError = { messageId: "avoidSizeInLoop", }; -ruleTester.run("getting-collection-size-in-collection", rule, { +ruleTester.run("avoid-getting-size-collection-in-loop", rule, { valid: [ // size/length assigned before loop, not used in loop ` diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java index 8ed5b81..900c554 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java @@ -37,6 +37,7 @@ public static List> getAllChecks() { AvoidAutoPlay.class, AvoidBrightnessOverride.class, AvoidCSSAnimations.class, + AvoidGettingSizeCollectionInLoop.class, AvoidHighAccuracyGeolocation.class, AvoidKeepAwake.class, LimitDbQueryResult.class, diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidGettingSizeCollectionInLoop.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidGettingSizeCollectionInLoop.java new file mode 100644 index 0000000..727d987 --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidGettingSizeCollectionInLoop.java @@ -0,0 +1,38 @@ +/* + * Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs + * Copyright © 2023 Green Code Initiative (https://green-code-initiative.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import org.greencodeinitiative.creedengo.javascript.DeprecatedEcoCodeRule; +import org.sonar.check.Rule; +import org.sonar.plugins.javascript.api.EslintBasedCheck; +import org.sonar.plugins.javascript.api.JavaScriptRule; +import org.sonar.plugins.javascript.api.TypeScriptRule; + +@JavaScriptRule +@TypeScriptRule +@Rule(key = AvoidGettingSizeCollectionInLoop.RULE_KEY) +public class AvoidGettingSizeCollectionInLoop implements EslintBasedCheck { + + public static final String RULE_KEY = "GCI3"; + + @Override + public String eslintKey() { + return "@creedengo/avoid-getting-size-collection-in-loop"; + } + +} diff --git a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json index af1a0b3..caa1260 100644 --- a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json +++ b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json @@ -1,6 +1,7 @@ { "name": "Creedengo", "ruleKeys": [ + "GCI3", "GCI9", "GCI11", "GCI12", diff --git a/test-project/src/avoid-getting-size-collection-in-loop.js b/test-project/src/avoid-getting-size-collection-in-loop.js new file mode 100644 index 0000000..6dbc5e7 --- /dev/null +++ b/test-project/src/avoid-getting-size-collection-in-loop.js @@ -0,0 +1,10 @@ +// Non-compliant: the collection size is accessed (twice) in the loop +for (let i = 0; i < array.length; i++) { + process("test"); // Noncompliant +} + +// Compliant: no collection size access in loop +const arrayLength = array.length; // Fetch the length once +for (let i = 0; i < arrayLength; i++) { + console.log("yay"); // Compliant +} From 0188eabbb39729495b976b54788c597963abace7 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Tue, 20 May 2025 18:36:31 +0200 Subject: [PATCH 3/8] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b23234..4738dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [#51](https://github.com/green-code-initiative/creedengo-javascript/pull/51) Add ESLint v9 and flat config support +- [#88](https://github.com/green-code-initiative/creedengo-javascript/pull/88) Add rule `@creedengo/avoid-gettint-size-collection-in-loop` (GCI3) ### Changed From 62b17773355a2de683860dc4e200a70e3b6ccd33 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Wed, 21 May 2025 09:51:13 +0200 Subject: [PATCH 4/8] add readme dep --- eslint-plugin/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint-plugin/README.md b/eslint-plugin/README.md index f4cba47..7e5c066 100644 --- a/eslint-plugin/README.md +++ b/eslint-plugin/README.md @@ -114,6 +114,7 @@ If your project uses a legacy ESLint version, it may use as well the now depreca | [avoid-autoplay](docs/rules/avoid-autoplay.md) | Avoid autoplay for videos and audio content | ✅ | | [avoid-brightness-override](docs/rules/avoid-brightness-override.md) | Should avoid to override brightness | ✅ | | [avoid-css-animations](docs/rules/avoid-css-animations.md) | Avoid usage of CSS animations | ✅ | +| [avoid-getting-size-collection-in-loop](docs/rules/avoid-getting-size-collection-in-loop.md) | Avoid getting size collection in loop | ✅ | | [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications | ✅ | | [avoid-keep-awake](docs/rules/avoid-keep-awake.md) | Avoid screen keep awake | ✅ | | [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ | From badcd79f747783d887831c52307527bd38dcc114 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Wed, 21 May 2025 10:08:12 +0200 Subject: [PATCH 5/8] add rule coverage for every collection callback method --- .../avoid-getting-size-collection-in-loop.js | 2 +- .../avoid-getting-size-collection-in-loop.js | 82 +++++++++++++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js index 22e6a7a..b3ea481 100644 --- a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js @@ -34,7 +34,7 @@ module.exports = { }, create: function (context) { const SIZE_PROPERTIES = ["length", "size"]; // We only include static analysis on size and length properties at the moment - const CALLBACK_METHODS = ["map", "forEach", "filter", "some", "every"]; + const CALLBACK_METHODS = ["filter", "find", "findIndex", "findLast", "findLastIndex", "some", "every", "flatMap", "forEach", "map", "reduce", "reduceRight", "some"]; /** * Checks if a node is a .length or .size property access (dot or bracket notation). diff --git a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js index ce84c4b..87f7872 100644 --- a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js @@ -182,10 +182,10 @@ ruleTester.run("avoid-getting-size-collection-in-loop", rule, { errors: [expectedError], }, - // 5. In callback of array methods + // 5. In callback of collection methods { code: ` - arr.forEach(item => { + arr.filter(item => { doSomething(arr.length); }); `, @@ -193,19 +193,89 @@ ruleTester.run("avoid-getting-size-collection-in-loop", rule, { }, { code: ` - arr.map(function(x) { - return set.size; + arr.find(item => { + doSomething(arr.length); }); `, errors: [expectedError], }, { code: ` - arr.filter(x => arr.size()); + arr.findIndex(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.findLast(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.findLastIndex(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.some(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.every(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.flatMap(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + }, + { + code: ` + arr.forEach(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + },{ + code: ` + arr.map(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + },{ + code: ` + arr.reduce(item => { + doSomething(arr.length); + }); + `, + errors: [expectedError], + },{ + code: ` + arr.reduceRight(item => { + doSomething(arr.length); + }); `, errors: [expectedError], }, - // 6. Multiple in one loop { code: ` From 8c1f3b29a9424aa65de79f60203e4280ef52b6b3 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Wed, 21 May 2025 10:46:01 +0200 Subject: [PATCH 6/8] running doc generator --- eslint-plugin/README.md | 36 +++++++++---------- .../avoid-getting-size-collection-in-loop.md | 6 +++- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/eslint-plugin/README.md b/eslint-plugin/README.md index 7e5c066..f4a2f8a 100644 --- a/eslint-plugin/README.md +++ b/eslint-plugin/README.md @@ -109,24 +109,24 @@ If your project uses a legacy ESLint version, it may use as well the now depreca ✅ Set in the `flat/recommended` configuration.\ ✅ Set in the `recommended` configuration. -| Name | Description | ⚠️ | -| :--------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :-- | -| [avoid-autoplay](docs/rules/avoid-autoplay.md) | Avoid autoplay for videos and audio content | ✅ | -| [avoid-brightness-override](docs/rules/avoid-brightness-override.md) | Should avoid to override brightness | ✅ | -| [avoid-css-animations](docs/rules/avoid-css-animations.md) | Avoid usage of CSS animations | ✅ | -| [avoid-getting-size-collection-in-loop](docs/rules/avoid-getting-size-collection-in-loop.md) | Avoid getting size collection in loop | ✅ | -| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications | ✅ | -| [avoid-keep-awake](docs/rules/avoid-keep-awake.md) | Avoid screen keep awake | ✅ | -| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ | -| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ | -| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ | -| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ | -| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ | -| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ | -| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination | ✅ | -| [prefer-lighter-formats-for-image-files](docs/rules/prefer-lighter-formats-for-image-files.md) | Prefer lighter formats for image files | ✅ | -| [prefer-shorthand-css-notations](docs/rules/prefer-shorthand-css-notations.md) | Encourage usage of shorthand CSS notations | ✅ | -| [provide-print-css](docs/rules/provide-print-css.md) | Enforce providing a print stylesheet | ✅ | +| Name                                   | Description | ⚠️ | +| :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | :-- | +| [avoid-autoplay](docs/rules/avoid-autoplay.md) | Avoid autoplay for videos and audio content | ✅ | +| [avoid-brightness-override](docs/rules/avoid-brightness-override.md) | Should avoid to override brightness | ✅ | +| [avoid-css-animations](docs/rules/avoid-css-animations.md) | Avoid usage of CSS animations | ✅ | +| [avoid-getting-size-collection-in-loop](docs/rules/avoid-getting-size-collection-in-loop.md) | Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback. | ✅ | +| [avoid-high-accuracy-geolocation](docs/rules/avoid-high-accuracy-geolocation.md) | Avoid using high accuracy geolocation in web applications | ✅ | +| [avoid-keep-awake](docs/rules/avoid-keep-awake.md) | Avoid screen keep awake | ✅ | +| [limit-db-query-results](docs/rules/limit-db-query-results.md) | Should limit the number of returns for a SQL query | ✅ | +| [no-empty-image-src-attribute](docs/rules/no-empty-image-src-attribute.md) | Disallow usage of image with empty source attribute | ✅ | +| [no-import-all-from-library](docs/rules/no-import-all-from-library.md) | Should not import all from library | ✅ | +| [no-multiple-access-dom-element](docs/rules/no-multiple-access-dom-element.md) | Disallow multiple access of same DOM element | ✅ | +| [no-multiple-style-changes](docs/rules/no-multiple-style-changes.md) | Disallow multiple style changes at once | ✅ | +| [no-torch](docs/rules/no-torch.md) | Should not programmatically enable torch mode | ✅ | +| [prefer-collections-with-pagination](docs/rules/prefer-collections-with-pagination.md) | Prefer API collections with pagination | ✅ | +| [prefer-lighter-formats-for-image-files](docs/rules/prefer-lighter-formats-for-image-files.md) | Prefer lighter formats for image files | ✅ | +| [prefer-shorthand-css-notations](docs/rules/prefer-shorthand-css-notations.md) | Encourage usage of shorthand CSS notations | ✅ | +| [provide-print-css](docs/rules/provide-print-css.md) | Enforce providing a print stylesheet | ✅ | diff --git a/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md b/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md index fadcefb..0d07ff5 100644 --- a/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md +++ b/eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md @@ -1,4 +1,8 @@ -# Should avoid getting the collection size in loop +# Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback (`@creedengo/avoid-getting-size-collection-in-loop`) + +⚠️ This rule _warns_ in the following configs: ✅ `flat/recommended`, ✅ `recommended`. + + ## Why is this an issue ? From 08aebb3d4bce9cd4a9332416335ac062150c8476 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Wed, 21 May 2025 10:53:50 +0200 Subject: [PATCH 7/8] add new test case + lint --- .../avoid-getting-size-collection-in-loop.js | 30 +++++++++++++++---- .../avoid-getting-size-collection-in-loop.js | 17 ++++++++--- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js index b3ea481..cf20760 100644 --- a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js @@ -23,25 +23,44 @@ module.exports = { meta: { type: "suggestion", docs: { - description: "Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback.", + description: + "Avoid getting the size/length of the collection in loops and callbacks. Assign it to a variable before the loop/callback.", category: "eco-design", recommended: "warn", }, messages: { - avoidSizeInLoop: "Avoid getting the size/length of the collection in the loop or callback. Assign it to a variable before the loop/callback.", + avoidSizeInLoop: + "Avoid getting the size/length of the collection in the loop or callback. Assign it to a variable before the loop/callback.", }, schema: [], }, create: function (context) { const SIZE_PROPERTIES = ["length", "size"]; // We only include static analysis on size and length properties at the moment - const CALLBACK_METHODS = ["filter", "find", "findIndex", "findLast", "findLastIndex", "some", "every", "flatMap", "forEach", "map", "reduce", "reduceRight", "some"]; + const CALLBACK_METHODS = [ + "filter", + "find", + "findIndex", + "findLast", + "findLastIndex", + "some", + "every", + "flatMap", + "forEach", + "map", + "reduce", + "reduceRight", + "some", + ]; /** * Checks if a node is a .length or .size property access (dot or bracket notation). * If you want to only match dot notation, keep !node.computed. */ function isSizeOrLengthMember(node) { - return node.type === "MemberExpression" && SIZE_PROPERTIES.includes(node.property.name); + return ( + node.type === "MemberExpression" && + SIZE_PROPERTIES.includes(node.property.name) + ); } /** @@ -77,7 +96,8 @@ module.exports = { if ( node.arguments && node.arguments.length > 0 && - (node.arguments[0].type === "FunctionExpression" || node.arguments[0].type === "ArrowFunctionExpression") + (node.arguments[0].type === "FunctionExpression" || + node.arguments[0].type === "ArrowFunctionExpression") ) { checkNodeRecursively(node.arguments[0].body); } diff --git a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js index 87f7872..e125b16 100644 --- a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js @@ -36,13 +36,19 @@ const expectedError = { ruleTester.run("avoid-getting-size-collection-in-loop", rule, { valid: [ - // size/length assigned before loop, not used in loop + // size/length assigned before loop, not used in loop body or test ` const n = arr.length; for (let i = 0; i < n; i++) { doSomething(arr[i]); } `, + ` + const n = arr.length; + for (let i = n; i < n + 5; i++) { + doSomething(arr[i - 5]); + } + `, // unrelated property in loop condition ` for (let i = 0; i < arr.customProp; i++) { @@ -254,21 +260,24 @@ ruleTester.run("avoid-getting-size-collection-in-loop", rule, { }); `, errors: [expectedError], - },{ + }, + { code: ` arr.map(item => { doSomething(arr.length); }); `, errors: [expectedError], - },{ + }, + { code: ` arr.reduce(item => { doSomething(arr.length); }); `, errors: [expectedError], - },{ + }, + { code: ` arr.reduceRight(item => { doSomething(arr.length); From a7746977640adc731ee812d109122093ea52b4d1 Mon Sep 17 00:00:00 2001 From: Mathis Girault Date: Wed, 21 May 2025 11:26:54 +0200 Subject: [PATCH 8/8] remove duplicate some + added test case --- .../lib/rules/avoid-getting-size-collection-in-loop.js | 1 - .../tests/lib/rules/avoid-getting-size-collection-in-loop.js | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js index cf20760..e988be9 100644 --- a/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js @@ -42,7 +42,6 @@ module.exports = { "findIndex", "findLast", "findLastIndex", - "some", "every", "flatMap", "forEach", diff --git a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js index e125b16..5bb5b98 100644 --- a/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js +++ b/eslint-plugin/tests/lib/rules/avoid-getting-size-collection-in-loop.js @@ -49,6 +49,11 @@ ruleTester.run("avoid-getting-size-collection-in-loop", rule, { doSomething(arr[i - 5]); } `, + ` + for (let i = n, n = arr.length; i < n + 5; i++) { + doSomething(arr[i - 5]); + } + `, // unrelated property in loop condition ` for (let i = 0; i < arr.customProp; i++) {