From 984c926b5e23b8e2f6a04c9091eafe4c1a01cb15 Mon Sep 17 00:00:00 2001 From: Safaa Ougunir Date: Wed, 21 May 2025 13:22:02 +0200 Subject: [PATCH] feat: add avoid using resource hungry libs --- CHANGELOG.md | 1 + .../rules/avoid-using-resource-hungry-libs.md | 30 +++++ .../rules/avoid-using-resource-hungry-libs.js | 112 ++++++++++++++++++ .../rules/avoid-using-resource-hungry-libs.js | 87 ++++++++++++++ .../creedengo/javascript/CheckList.java | 3 +- .../checks/AvoidUsingResourceHungryLibs.java | 37 ++++++ 6 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 eslint-plugin/docs/rules/avoid-using-resource-hungry-libs.md create mode 100644 eslint-plugin/lib/rules/avoid-using-resource-hungry-libs.js create mode 100644 eslint-plugin/tests/lib/rules/avoid-using-resource-hungry-libs.js create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidUsingResourceHungryLibs.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b23234..ac6d093 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 +- [#90](https://github.com/green-code-initiative/creedengo-javascript/pull/90) Add avoid using resource-hungry libs rule ### Changed diff --git a/eslint-plugin/docs/rules/avoid-using-resource-hungry-libs.md b/eslint-plugin/docs/rules/avoid-using-resource-hungry-libs.md new file mode 100644 index 0000000..706ed2a --- /dev/null +++ b/eslint-plugin/docs/rules/avoid-using-resource-hungry-libs.md @@ -0,0 +1,30 @@ +# Avoid using resource hungry libraries(`@creedengo/avoid-using-resource-hungry-libs`) + +⚠️ This rule _warns_ in the following configs: ✅ `flat/recommended`, ✅ `recommended`. + + + +## Why is this an issue? + +Avoid imports of known resource-hungry or deprecated libraries and suggest lighter alternatives. + +This rule flags any import, require or dynamic import of libraries listed in the project’s deprecation dictionary (`moment`, `iconsax`, etc.), because these libraries +1. Inflate your bundle size +2. Slow parsing & compilation +3. Often lack tree-shaking support +4. May be unmaintained + +```js +import moment from "moment"; +const age = moment().diff(birthday, "years"); +``` + +```js +import differenceInYears from "date-fns/differenceInYears"; + +const age = differenceInYears(new Date(), birthday); +``` + +## Resources + +### Documentation diff --git a/eslint-plugin/lib/rules/avoid-using-resource-hungry-libs.js b/eslint-plugin/lib/rules/avoid-using-resource-hungry-libs.js new file mode 100644 index 0000000..cb76b99 --- /dev/null +++ b/eslint-plugin/lib/rules/avoid-using-resource-hungry-libs.js @@ -0,0 +1,112 @@ +/* + * 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"; + +/** + * List of deprecated libraries and their suggested replacements. + * + * Key = package name to forbid + * Value = { reason: string, alternatives: string[] } + */ +const DEPRECATED_LIBS = { + moment: { + reason: "deprecated and heavy to load", + alternatives: ["date-fns", "dayjs"], + }, + iconsax: { + reason: "does not support tree-shaking and adds large bundle size", + alternatives: ["@mui/icons-material", "lucide-react"], + }, + lodash: { + reason: "too large, prefer modular imports or native methods", + alternatives: ["lodash.get", "Array.prototype.map", "Object.assign"], + }, + // add more entries here as needed +}; + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: + "Disallow importing deprecated libraries and suggest lighter alternatives", + category: "best-practice", + recommended: "warn", + }, + messages: { + noDeprecatedLib: + "Library '{{name}}' is {{reason}}. Consider using {{alternatives}}.", + }, + schema: [], + }, + + create(context) { + /** + * Report a deprecation warning on the given node. + */ + function reportDeprecation(node, libName) { + const { reason, alternatives } = DEPRECATED_LIBS[libName]; + context.report({ + node, + messageId: "noDeprecatedLib", + data: { + name: libName, + reason, + alternatives: alternatives.join(", "), + }, + }); + } + + return { + // import moment from "moment"; + ImportDeclaration(node) { + const pkg = node.source.value; + if (DEPRECATED_LIBS[pkg]) { + reportDeprecation(node.source, pkg); + } + }, + + // const moment = require("moment"); + CallExpression(node) { + if ( + node.callee.type === "Identifier" && + node.callee.name === "require" && + node.arguments.length === 1 + ) { + const arg = node.arguments[0]; + if ( + arg.type === "Literal" && + typeof arg.value === "string" && + DEPRECATED_LIBS[arg.value] + ) { + reportDeprecation(arg, arg.value); + } + } + }, + + // import("moment").then(...) + ImportExpression(node) { + const src = node.source; + if (src.type === "Literal" && DEPRECATED_LIBS[src.value]) { + reportDeprecation(src, src.value); + } + }, + }; + }, +}; diff --git a/eslint-plugin/tests/lib/rules/avoid-using-resource-hungry-libs.js b/eslint-plugin/tests/lib/rules/avoid-using-resource-hungry-libs.js new file mode 100644 index 0000000..8a7ef02 --- /dev/null +++ b/eslint-plugin/tests/lib/rules/avoid-using-resource-hungry-libs.js @@ -0,0 +1,87 @@ +/* + * 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/avoid-using-resource-hungry-libs"); +const { RuleTester } = require("eslint"); + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, +}); + +const momentError = { + messageId: "noDeprecatedLib", + data: { + name: "moment", + reason: "deprecated and heavy to load", + alternatives: "date-fns, dayjs", + }, + type: "Literal", +}; + +const iconsaxError = { + messageId: "noDeprecatedLib", + data: { + name: "iconsax", + reason: "does not support tree-shaking and adds large bundle size", + alternatives: "@mui/icons-material, lucide-react", + }, + type: "Literal", +}; + +ruleTester.run("no-deprecated-libs", rule, { + valid: [ + // imports of non-deprecated libraries + `import dayjs from "dayjs";`, + `const { format } = require("date-fns");`, + `const moment = require("luxon");`, + `import HomeIcon from "@mui/icons-material/Home";`, + `import { ReactComponent as Icon } from "some-svg";`, + `import("dayjs").then(d => console.log(d));`, + // dynamic import of non-deprecated + `import("lucide-react").then(l => console.log(l));`, + ], + invalid: [ + // -- moment cases -- + { code: `import moment from "moment";`, errors: [momentError] }, + { code: `import { duration } from "moment";`, errors: [momentError] }, + { code: `import "moment";`, errors: [momentError] }, + { code: `const moment = require("moment");`, errors: [momentError] }, + { code: `require("moment");`, errors: [momentError] }, + { + code: `import("moment").then(m => console.log(m));`, + errors: [momentError], + }, + + // -- iconsax cases -- + { code: `import iconsax from "iconsax";`, errors: [iconsaxError] }, + { code: `import { IconHome } from "iconsax";`, errors: [iconsaxError] }, + { code: `import "iconsax";`, errors: [iconsaxError] }, + { code: `const iconsax = require("iconsax");`, errors: [iconsaxError] }, + { code: `require("iconsax");`, errors: [iconsaxError] }, + { + code: `import("iconsax").then(i => console.log(i));`, + errors: [iconsaxError], + }, + ], +}); 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..5fa8b79 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 @@ -48,7 +48,8 @@ public static List> getAllChecks() { PreferCollectionsWithPagination.class, PreferLighterFormatsForImageFiles.class, PreferShorthandCSSNotations.class, - ProvidePrintCSS.class + ProvidePrintCSS.class, + AvoidUsingResourceHungryLibs.class ); } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidUsingResourceHungryLibs.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidUsingResourceHungryLibs.java new file mode 100644 index 0000000..97aa616 --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/AvoidUsingResourceHungryLibs.java @@ -0,0 +1,37 @@ +/* + * 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.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 = AvoidUsingResourceHungryLibs.RULE_KEY) +public class AvoidUsingResourceHungryLibs implements EslintBasedCheck { + + public static final String RULE_KEY = "GCI36"; + + @Override + public String eslintKey() { + return "@creedengo/avoid-autoplay"; + } + +}