diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b23234..66b9bf8 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
+- [#84](https://github.com/green-code-initiative/creedengo-javascript/pull/84) Add rule GCI535 "No imported number format library"
### Changed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b552cd4..36761ce 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,9 +42,10 @@ But it can be useful to prepare a test project to check the correct execution of
## Installation
1. Clone the Git repository
-2. Run `yarn install` inside **eslint-plugin** directory
-3. Synchronize dependencies using Maven inside **sonar-plugin** directory
-4. You are good to go! π
+1. Inside **eslint-plugin** directory, run `yarn install`
+1. Inside **sonar-plugin** directory, synchronize dependencies using Maven with `mvn clean install -DskipTests`
+1. Inside root directory, initialize docker with `docker compose up --build -d`
+1. You are good to go! π
## Create a rule
diff --git a/eslint-plugin/README.md b/eslint-plugin/README.md
index f4cba47..2d27c72 100644
--- a/eslint-plugin/README.md
+++ b/eslint-plugin/README.md
@@ -119,6 +119,7 @@ If your project uses a legacy ESLint version, it may use as well the now depreca
| [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-imported-number-format-library](docs/rules/no-imported-number-format-library.md) | You should not format number with an external 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 | β
|
diff --git a/eslint-plugin/docs/rules/no-imported-number-format-library.md b/eslint-plugin/docs/rules/no-imported-number-format-library.md
new file mode 100644
index 0000000..bd1d44b
--- /dev/null
+++ b/eslint-plugin/docs/rules/no-imported-number-format-library.md
@@ -0,0 +1,56 @@
+# You should not format number with an external library (`@creedengo/no-imported-number-format-library`)
+
+β οΈ This rule _warns_ in the following configs: β
`flat/recommended`, β
`recommended`.
+
+## Why is this an issue?
+
+Importing an external library for lightweight operations increases overall size of the program.
+Using native methods instead reduces the amount of memory and storage to run and store the application.
+This is especially critical in environments with limited resources, such as on mobile devices or in web applications
+where bandwidth and download times matter.
+
+Smaller programs generally have better runtime performance.
+Reducing the number of unnecessary modules minimizes the amount of code that needs to be interpreted or compiled,
+leading to faster execution and improved overall performance.
+
+Depending on less external dependencies also increases the maintainability and security of your program.
+
+## Examples
+
+**Example with the [numbro](https://numbrojs.com/) library, when you use
+`format` method.**
+
+```js
+// Example with numbro (not compliant)
+import numbro from "numbro";
+
+numbro.setLanguage('en-GB');
+var string = numbro(1000).format({
+ thousandSeparated: true,
+}); // '1,000'
+
+// Example with numerable (not compliant)
+import { format } from "numerable";
+format(1000, '0,0');
+
+// Example with Intl (compliant)
+new Intl.NumberFormat("en-GB").format(1000); // '1,000'
+```
+
+## Limitations
+As for now, only two libraries are handled by this rule :
+- [numbro](https://numbrojs.com/)
+- [numerable](https://numerablejs.com/lander)
+
+Some candidates for the future developments are :
+- [javascript-number-formatter](https://github.com/Mottie/javascript-number-formatter)
+- [numeraljs](https://www.npmjs.com/package/numerable)
+- [formatjs](https://formatjs.github.io/)
+
+Itβs more likely this rule wonβt ever be exhaustive.
+
+## Resources
+
+### Documentation
+
+- [Mozilla Web Technology for Developers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) -
diff --git a/eslint-plugin/lib/rules/no-imported-number-format-library.js b/eslint-plugin/lib/rules/no-imported-number-format-library.js
new file mode 100644
index 0000000..be8c048
--- /dev/null
+++ b/eslint-plugin/lib/rules/no-imported-number-format-library.js
@@ -0,0 +1,78 @@
+/*
+ * 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: "You should not format number with an external library",
+ category: "eco-design",
+ recommended: "warn",
+ },
+ messages: {
+ ShouldNotUseImportedNumberFormatLibrary:
+ "You should not format number with an external library",
+ },
+ schema: [],
+ },
+
+ create: function (context) {
+ let variablesNumbro = [];
+
+ const errorReport = (node) => ({
+ node,
+ messageId: "ShouldNotUseImportedNumberFormatLibrary",
+ });
+
+ return {
+ VariableDeclarator(node) {
+ if (node.init.callee?.name === "numbro") {
+ variablesNumbro.push(node.id.name);
+ }
+ },
+ CallExpression(node) {
+ const formatIsCalledOnANumbroTypeVariable =
+ node.callee.type === "MemberExpression" &&
+ variablesNumbro.includes(node.callee.object.name) &&
+ node.callee.property.name === "format";
+ if (formatIsCalledOnANumbroTypeVariable) {
+ context.report(errorReport(node.callee.property));
+ }
+ let formatIsCalledOnNumbroInstance =
+ node.parent.type === "MemberExpression" &&
+ node.callee.name === "numbro" &&
+ node.parent.property.name === "format";
+ if (formatIsCalledOnNumbroInstance) {
+ context.report(errorReport(node.parent.property));
+ }
+ },
+ ImportDeclaration(node) {
+ const importedLibraryName = node.source.value;
+ if (importedLibraryName === 'numerable') {
+ const formatSpecifier = node.specifiers.find(specifier => specifier.type === 'ImportSpecifier' && specifier.imported.name === 'format');
+ if (formatSpecifier) {
+ context.report(errorReport(formatSpecifier));
+ }
+ }
+ }
+ };
+ },
+};
diff --git a/eslint-plugin/package.json b/eslint-plugin/package.json
index 1a363d9..8479303 100644
--- a/eslint-plugin/package.json
+++ b/eslint-plugin/package.json
@@ -29,6 +29,7 @@
"lint:fix": "eslint . --fix",
"pack:sonar": "npm pkg set main=\"./dist/rules.js\" && mkdirp dist/pack && yarn pack -o dist/pack/creedengo-eslint-plugin.tgz && npm pkg set main=\"./lib/standalone.js\"",
"test": "mocha tests --recursive",
+ "test:watch": "mocha tests --watch --recursive",
"test:cov": "nyc --reporter=lcov --reporter=text mocha tests --recursive",
"update:eslint-docs": "eslint-doc-generator"
},
diff --git a/eslint-plugin/tests/lib/rules/no-imported-number-format-library.js b/eslint-plugin/tests/lib/rules/no-imported-number-format-library.js
new file mode 100644
index 0000000..2f8719d
--- /dev/null
+++ b/eslint-plugin/tests/lib/rules/no-imported-number-format-library.js
@@ -0,0 +1,88 @@
+/*
+ * 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/no-imported-number-format-library");
+const RuleTester = require("eslint").RuleTester;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ sourceType: "module",
+ },
+});
+const expectedIdentifierError = {
+ messageId: "ShouldNotUseImportedNumberFormatLibrary",
+ type: "Identifier",
+};
+const expectedImportError = {
+ messageId: "ShouldNotUseImportedNumberFormatLibrary",
+ type: "ImportSpecifier",
+};
+ruleTester.run("no-imported-number-format-library", rule, {
+ valid: [
+ "new Intl.NumberFormat().format(1000);",
+ "numbro(1000).add(5);",
+ `
+ const number = numbro(1000);
+ const number2 = numbro(2000);
+ number2.add(1000);
+ `,
+ "import { parse } from 'numerable';",
+ "import { format } from 'date-fns';",
+ "import mysql from 'mysql2';"
+ ],
+ invalid: [
+ {
+ code: "numbro(1000).format({thousandSeparated: true});",
+ errors: [expectedIdentifierError],
+ },
+ {
+ code: `
+ const number = numbro(1000);
+ number.format({thousandSeparated: true});
+ `,
+ errors: [expectedIdentifierError],
+ },
+ {
+ code: `
+ const number = numbro(1000);
+ const number2 = numbro(2000);
+ number.format({thousandSeparated: true});
+ `,
+ errors: [expectedIdentifierError],
+ },
+ {
+ code: "import { format } from 'numerable';",
+ errors: [expectedImportError],
+ },
+ {
+ code: "import { format as myFormat} from 'numerable';",
+ errors: [expectedImportError],
+ },
+ ],
+});
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..4c2ba7b 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
@@ -42,6 +42,7 @@ public static List> getAllChecks() {
LimitDbQueryResult.class,
NoEmptyImageSrcAttribute.class,
NoImportAllFromLibrary.class,
+ NoImportedNumberFormatLibrary.class,
NoMultipleAccessDomElement.class,
NoMultipleStyleChanges.class,
NoTorch.class,
diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/NoImportedNumberFormatLibrary.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/NoImportedNumberFormatLibrary.java
new file mode 100644
index 0000000..6afd905
--- /dev/null
+++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/NoImportedNumberFormatLibrary.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 = NoImportedNumberFormatLibrary.RULE_KEY)
+public class NoImportedNumberFormatLibrary implements EslintBasedCheck {
+
+ public static final String RULE_KEY = "GCI535";
+
+ @Override
+ public String eslintKey() {
+ return "@creedengo/no-imported-number-format-library";
+ }
+
+}
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..224851c 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": [
+ "GCI535",
"GCI9",
"GCI11",
"GCI12",
diff --git a/test-project/README.md b/test-project/README.md
index 7fc6f5d..2db85f0 100644
--- a/test-project/README.md
+++ b/test-project/README.md
@@ -28,7 +28,7 @@ Use the following Shell script which will do the job for you:
Or you can manually run these commands:
- Install dependencies: `yarn install`
-- Start Sonar Scanner: `yarn sonar -Dsonar.token=MY_SONAR_TOKEN`
+- Start Sonar Scanner: `yarn sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.token=MY_SONAR_TOKEN`
### 3. Check errors
diff --git a/test-project/src/no-imported-number-format-library.js b/test-project/src/no-imported-number-format-library.js
new file mode 100644
index 0000000..664c2e4
--- /dev/null
+++ b/test-project/src/no-imported-number-format-library.js
@@ -0,0 +1,14 @@
+import numbro from "numbro";
+import { parse, format } from "numerable"; // Non-compliant: usage of external library to format number
+
+numbro(1000).format({thousandSeparated: true}); // Non-compliant: usage of external library to format number
+
+let variable = numbro(1000);
+variable.format({thousandSeparated: true}); // Non-compliant: usage of external library to format number
+
+numbro(2000).add(1000); // Compliant
+
+new Intl.NumberFormat().format(1000); // Compliant
+
+format(28);
+parse("29"); // Compliant
diff --git a/test-project/tool_send_to_sonar.sh b/test-project/tool_send_to_sonar.sh
index dcbc22b..174c810 100755
--- a/test-project/tool_send_to_sonar.sh
+++ b/test-project/tool_send_to_sonar.sh
@@ -7,4 +7,4 @@
yarn install
# sending to Sonar phase
-yarn sonar -Dsonar.host.url=http://0.0.0.0:9000 -Dsonar.token=$1
+yarn sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.token=$1