Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 18 additions & 17 deletions eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +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-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 | ✅ |

<!-- end auto-generated rules list -->

Expand Down
26 changes: 26 additions & 0 deletions eslint-plugin/docs/rules/avoid-getting-size-collection-in-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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`.

<!-- end auto-generated rule header -->

## 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]);
}
```
135 changes: 135 additions & 0 deletions eslint-plugin/lib/rules/avoid-getting-size-collection-in-loop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

"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 = [
"filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"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)
);
}

/**
* 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);
}
},
};
},
};
Loading
Loading